使用重载构造函数进行赋值

时间:2014-01-24 18:45:52

标签: c++ arrays

我创建了一个自定义矢量类,它使用动态数组来存储数据。重载的构造函数将指向现有数组和数组大小的指针作为参数。

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;
    };
}

2 个答案:

答案 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_iteratorreverse_iteratorconst_reverse_iterator类,并添加cbegin / cendrbegin / rendcrbegin / crend支持数据的常量和/或反向迭代。

但请注意,大部分内容只是重复std::vector已提供的内容。我们在这里添加的唯一新功能是采用数组并自动推断其大小的ctor。同时,这足以提供一个固定大小的数组包装器(除了动态大小调整)与std::vector具有近似的奇偶校验。