我创建了一个自定义矢量类,它使用动态数组来存储数据。重载的构造函数将指向现有数组和数组大小的指针作为参数。
int a[3] = { 1, 2, 3 };
Vector<int> v(a, 3);
但是,当我尝试使用以下代码更改此向量时,它会崩溃,因为向量对象“v”的指针指向动态数组地址的0xcccccccc
v = Vector<int>(a, 3);
为什么会发生这种情况,如何改进上述作业?
编辑:这是calss代码:
template <class T> class Vector
{
private:
T* mArray;
int Length;
public:
Vector(){
mArray = 0;
Length = 0;
};
Vector(const Vector& rVectorData){
Length = rVectorData.Length;
T* pArray = new T[Length];
for (int i = 0; i < Length; i++)
pArray[i] = rVectorData.mArray[i];
delete[] Array;
mArray = pArray;
};
Vector(const T* aArray, int size){
Length = size;
T* pArray = new T[Length];
for (int i = 0; i < Length; i++)
pArray[i] = aArray[i];
delete[] mArray;
mArray = pArray;
};
~Vector(){
delete[] mArray;
mArray = 0;
Length = 0;
};
}
答案 0 :(得分:1)
delete[] mArray;
mArray = pArray;
由于这是在构造函数中发生的,并且您尚未将mArray
初始化为任何内容(例如nullptr
),因此您试图删除一些随机的内存区域(您没有分配),这可能会导致你的程序崩溃(它是UB)。
您可以通过删除delete[] mArray
行来解决此问题,因为构造函数只会在构造期间被调用。
“v”指向0xcccccccc的数组地址“a”
由于您在构造函数中分配内存,v
将不会指向a
的地址,因为您要将值从a
复制到v
,已经分配了自己的记忆。
此外,由于您没有定义复制构造函数,因此无论何时尝试复制矢量,它都会执行浅复制。这将导致内存损坏问题,因为无论哪个变量超出范围,首先都会释放内存,而在另一个中留下悬空指针。当后者最终超出范围时,它也会导致UB(并可能导致程序崩溃)。
正如Karoly所说,当你实现自己的析构函数时,你应该遵循3的规则。
答案 1 :(得分:1)
在我看来,如果你要编写自己的矢量类(或几乎任何已经广泛使用的东西的半复制品),你应该尝试不仅使它正确,而且添加的东西混合新手,所以你的代码不仅仅是对已经很容易获得的东西的平庸模仿(并且最好避免它在任何方向上向后退一步)。
例如,如果我要支持从数组初始化Vector,我会添加一个自动推断出数组大小的模板成员函数:
template <size_t N>
Vector(T (&array)[N]) : data(new T[N]), size(N) {
std::copy_n(array, N, data);
}
这允许类似:
int a[]={1, 2, 3};
Vector<int> x(a);
...所以你不必指定大小。
你已经听说过三人统治。为了避免从std::vector
退一步,您几乎肯定希望将其更新为规则5(或者使用更智能的指针类,让您遵循零规则)。
这样做的直接方法是实现移动ctor并移动赋值运算符:
Vector &operator=(Vector &&src) {
delete[] data;
data=src.data;
size=src.size;
src.data=nullptr;
src.size = 0;
return *this;
}
Vector(Vector &&src): data(src.data), size(src.size) {
src.data=nullptr;
src.size=0;
}
为方便起见,您几乎肯定也想要包含一个带有初始化列表的ctor:
Vector(std::initializer_list<T> const &i) : data(new T[i.size()]), size(i.size())
{
std::copy(i.begin(), i.end(), data);
}
最后,您只需要(或者至少真的需要)支持包含数据的迭代器接口:
class iterator {
T *pos;
friend class Vector;
iterator(T *init): pos(init) {}
public:
iterator &operator++() { ++pos; return *this; }
iterator &operator--() { --pos; return *this; }
iterator &operator++(int) { iterator tmp(*this); ++pos; return tmp; }
iterator &operator--(int) { iterator tmp(*this); --pos; return tmp; }
T &operator*() { return *pos; }
bool operator!=(iterator const &other) const { return pos!=other.pos; }
};
iterator begin() { return iterator(data); }
iterator end() { return iterator(data+size); }
...然后您要添加const_iterator
,reverse_iterator
和const_reverse_iterator
类,并添加cbegin
/ cend
,rbegin
/ rend
和crbegin
/ crend
支持数据的常量和/或反向迭代。
但请注意,大部分内容只是重复std::vector
已提供的内容。我们在这里添加的唯一新功能是采用数组并自动推断其大小的ctor。同时,这足以提供一个固定大小的数组包装器(除了动态大小调整)与std::vector
具有近似的奇偶校验。