这是一个有效的复制构造函数吗?

时间:2013-06-06 12:45:48

标签: c++ copy-constructor

我刚开始学习Rule of Three,并想知道以下方法是否足以支持复制构造函数:

Array<T, ROW, COL>(const Array<T, ROW, COL> &array) {
    *this = array;
}

如果还不够,为什么不呢?

编辑为每个请求添加了分配运算符

inline Array<T, ROW, COL>& operator=(const Array<T, ROW, COL> &other) {;
    for (int i = 0; i < ROW; ++i) {
        for (int j = 0; j < COL; ++j) {
            data[j*ROW + i] = other(i, j);
        }
    }

    return *this;
}

3 个答案:

答案 0 :(得分:4)

一般说明(在您编辑问题之前):

在复制构造函数中调用复制赋值运算符不是一般解决方案。它们意味着不同的东西(我的意思是,如果它们的意思相同,为什么它们应该在那里)。

  • 当(现有!)对象被赋予新值时,将调用赋值运算符。 如果此对象属于同一类型,我们也称之为复制赋值,因为典型的实现只是复制内容(但可能共享一些引用的东西,例如共享指针或带共享的PIMPL类)数据)。复制赋值运算符由编译器自动实现,除非您提供一个。它使用类型的赋值运算符复制每个成员(原始成员也被复制)。

  • 当(尚未存在!)obejct被赋予相同类型的初始值时,将调用复制构造函数,即应使用现有对象的副本对其进行初始化。同样,如果您不提供复制构造函数,编译器会为您生成一个,再次使用复制构造函数复制成员。

如果从复制构造函数中调用赋值运算符,则表示生成的程序在复制初始化新对象时执行以下步骤:

  • (除非您使用成员初始化列表:)它使用默认构造函数初始化非基本类成员。原始类型未初始化。
  • 然后,调用赋值运算符。如果您没有定义一个,则会复制所有成员。

因此 在大多数情况下都应该没问题,但有几种情况工作,特别是如果你的班级有的成员>不能默认构造或无法分配。如果是这种情况,并且它们仍然可以复制构造(与复制分配相反),则必须手动初始化复制构造函数的成员初始化列表中的成员。


编辑(由于问题已被编辑):在您的情况下,data是基本类型(所有指针都被认为是原始的),因此您必须在副本中正确初始化它调用赋值运算符之前的构造函数。如果不这样做,分配将删除未初始化的指针。所以你应该做到最好(避免代码重复;如果你这样做可能会更有效率):

Array<T, ROW, COL>(const Array<T, ROW, COL> &array) :
    data(0)  // Now it is at least initialized, although inefficient
{
    *this = array;
}

然后,赋值运算符将尝试删除空指针,这是可以的(它什么都不做),然后执行实际的复制。将data(0)初始化视为“默认构造的null Array<...>”对象,暂时考虑。 (也许你已经提供了一个分配外部存储器的空对象?)

答案 1 :(得分:3)

有一个重要原因导致你提出的代码不够:

  • 赋值运算符应用于初始化对象,但在复制构造函数中不是这种情况。具体来说,代码运行时data成员未正确初始化*this = arraydata不在成员初始化列表中),因此您正在调用delete[]运算符未初始化的data

它要求您至少在使用成员初始化程序列表之前初始化data成员。

你的建议有更多的缺点:

  • 赋值运算符强制限制T类型(但可能限制在类Array中):
    • T必须是默认可构造的(如leemes所述)。
  • 赋值运算符实现不好:
    • 没有考虑案件a = a(自我指派),正如Nick Bougalis所说。
  • 不是例外安全:
    • 如果new[]运算符失败,则对象将处于不一致状态。
    • 如果复制T抛出异常,则该对象将处于不一致状态。

在使用赋值运算符实现中的复制构造函数而不是相反的情况下,我看到了更好的方法:

Array<T, ROW, COL>(const Array<T, ROW, COL> &array): data(new T[ROW * COL]) {
    for (int i = 0; i < ROW; ++i) {
        for (int j = 0; j < COL; ++j) {
            data[j*ROW + i] = other(i, j);
        }
    }
}

Array<T, ROW, COL>& operator=(Array<T, ROW, COL> other) {
    swap(other);
    return *this;
}

void swap(Array<T, ROW, COL>& other) {
    T* tmp = data;
    data = other.data;
    other.data = tmp;
}

(这也称为复制和交换习语。)

答案 2 :(得分:1)

除了leemes的优秀帖子之外,请允许我添加一个注意事项。在operator=实现中,您应该考虑添加一个检查,以至少处理自我分配(即a = a;)。此外,如果您的data成员变量是一个指针(正如您的原始代码所示),那么您必须确保它指向一些有效的内存,因为如果您将其初始化为指向复制构造函数中的0,当您的赋值运算符尝试取消引用它时,它将崩溃。

inline Array<T, ROW, COL>& operator=(const Array<T, ROW, COL> &other) 
{
    if(this == &other)
        return *this; // nothing to do!

    /* You should make sure that either data already points to an allocated
     * buffer of the appropriate size, or you should allocate it at this point.
     */

    assert(data != NULL);

    for (int i = 0; i < ROW; ++i) {
        for (int j = 0; j < COL; ++j) {
            data[j*ROW + i] = other(i, j);
        }
    }

    return *this;
}