从复制构造函数调用默认赋值运算符是不好的形式?

Mar*_*arc 13 c++ constructor copy-constructor assignment-operator c++11

考虑一类需要复制的副本.副本中的绝大多数数据元素必须严格反映原始数据元素,但是有少数元素的状态不被保留且需要重新初始化.

从复制构造函数调用默认赋值运算符是不好的形式?

默认赋值运算符对于Plain Old Data(int,double,char,short)以及每个赋值运算符的用户定义类都表现良好.指针需要单独处理.

一个缺点是该方法使得赋值运算符瘫痪,因为不执行额外的重新初始化.也无法禁用赋值运算符的使用,从而通过使用不完整的默认赋值运算符打开用户创建损坏类的选项A obj1,obj2; obj2=obj1; /* Could result is an incorrectly initialized obj2 */.

a(orig.a),b(orig.b)...除了a(0),b(0) ...必须写之外,放宽要求是很好的.需要写入所有初始化两次会产生两个错误位置,如果double x,y,z要将新变量(例如)添加到类中,初始化代码需要在至少2个位置而不是1个位置正确添加.

有没有更好的办法?

在C++ 0x中有更好的方法吗?

class A {
  public:
    A(): a(0),b(0),c(0),d(0)
    A(const A & orig){
      *this = orig;       /* <----- is this "bad"? */
      c = int();
    }
  public:
    int a,b,c,d;
};

A X;
X.a = 123;
X.b = 456;
X.c = 789;
X.d = 987;

A Y(X);

printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d);
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d);
Run Code Online (Sandbox Code Playgroud)

输出:

X: 123 456 789 987
Y: 123 456 0 987
Run Code Online (Sandbox Code Playgroud)

备用复制构造函数:

A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){}  /* <-- is this "better"? */
Run Code Online (Sandbox Code Playgroud)

Jef*_*rdy 14

正如布朗指出的那样,你最好在复制结构方面实施任务.我更喜欢他的替代习语:

T& T::operator=(T t) {
    swap(*this, t);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

它有点短,可以利用一些深奥的语言功能来提高性能.像任何好的C++代码一样,它也有一些细微之处需要注意.

首先,t有意地通过值传递参数,这样就可以调用复制构造函数(大部分时间),我们可以在不影响原始值的情况下修改我们内心的内容.使用const T&将无法编译,并将T&通过修改assign-from值触发一些令人惊讶的行为.

这种技术还需要swap以不使用类型赋值运算符的方式专门用于类型(如同std::swap),否则将导致无限递归.小心任何杂散的using std::swapusing namespace std,因为他们将拉std::swap入的范围和造成的问题,如果你不擅长swapT.如果已定义,则过载分辨率和ADL将确保使用正确的交换版本.

有几种方法可以swap为类型定义.第一种方法使用swap成员函数来完成实际工作,并具有swap委托给它的特化,如下所示:

class T {
public:
    // ....
    void swap(T&) { ... }
};

void swap(T& a, T& b) { a.swap(b); }
Run Code Online (Sandbox Code Playgroud)

这在标准库中很常见; std::vector例如,以这种方式实现交换.如果你有一个swap成员函数,你可以直接从赋值运算符调用它,并避免函数查找的任何问题.

另一种方法是声明swap为友元函数并让它完成所有工作:

class T {
    // ....
    friend void swap(T& a, T& b);
};

void swap(T& a, T& b) { ... }
Run Code Online (Sandbox Code Playgroud)

我更喜欢第二个,因为swap()通常不是类接口的组成部分; 它似乎更适合作为自由功能.然而,这是一个品味问题.

swap对类型进行优化是实现C++ 0x中右值引用的一些好处的常用方法,因此如果类可以利用它并且您确实需要性能,那么通常这是一个好主意.

  • 这有潜在危险!如果没有明确的特化,std :: swap需要一个工作副本赋值运算符,并且可以在其实现中使用它.这将使此运算符=无限递归. (3认同)
  • 这就是ADL应该用于的:你在同一个头文件中定义T的命名空间中为T提供交换函数,这样如果你有`nm :: T`,你总是有`nm :: swap (T&,T&)`.然后调用者应该使用std :: swap; swap(myT,otherT);`,而不是`std :: swap(myT,otherT)`.如果他们调用`std :: swap`,至少`operator =`调用`nm :: swap`,所以它不是递归的,只是效率不高.我并没有大规模迷恋大会,但确实如此. (2认同)

Geo*_*che 4

使用您的复制构造函数版本,首先默认构造成员,然后分配成员。
对于整型,这并不重要,但如果您有像 s 这样的重要成员,那么std::string这是不必要的开销。

因此,是的,一般来说,您的替代复制构造函数更好,但如果您只有整型类型作为成员,那么这并不重要。