0

我刚刚开始学习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;
}
4

3 回答 3

4

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

仅仅在复制构造函数中调用复制赋值运算符并不是一个通用的解决方案。它们的意思不同(我的意思是,如果它们的意思相同,为什么要在那里)。

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

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

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

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

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


编辑(因为问题得到了编辑):在你的情况下,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<...>”对象,只是暂时的。(也许您已经提供了一个不分配外部内存的空对象?)

于 2013-06-06T13:00:37.737 回答
3

您提出的代码还不够有一个重要原因:

  • 赋值运算符应用于已初始化的 object,但在复制构造函数中并非如此。具体来说,data当代码运行时,您的成员未正确初始化*this = arraydata不在成员初始化器列表中),因此您delete[]在 uninitialized 上调用运算符data

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

您的建议还有更多缺点:

  • 赋值运算符强制限制类型(但也许这些限制无论如何 T都在类中):Array
    • T必须是默认可构造的(如 leemes 所述)。
  • 赋值运算符不好实现:
    • 正如 Nick Bougalis 指出的那样,没有考虑案例a = a(自我分配)。
  • 不是异常安全的:
    • 如果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;
}

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

于 2013-06-06T13:53:06.817 回答
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;
}
于 2013-06-06T13:25:55.203 回答