为类数据成员调用copy构造函数和operator =

时间:2013-12-30 17:20:04

标签: c++

以下是示例代码(仅用于学习目的)。类A和B是独立的,具有复制结构和运算符=。

class C
{

public:
   C(string cName1, string cName2): a(cName1), b(new B(cName2)) {}
   C(const C &c): a(c.a), b(new B(*(c.b))) {}
   ~C(){ delete b; }
   C& operator=(const C &c)
   {
      if(&c == this) return *this;

      a.operator=(c.a);

      //1
      delete b;
      b = new B(*(c.b));

      //What about this:
      /*

      //2
      b->operator=(*(c.b));

      //3
      (*b).operator=(*(c.b));

      */

      return *this;
   }

private:
   A a;
   B *b;

};

为数据成员b分配有三种方法。实际上他们中的第一个调用了复制构造函数。我应该使用哪一个? // 2和// 3似乎是等价的。

4 个答案:

答案 0 :(得分:2)

我决定将答案转移到答案并详细说明。

您想要使用2或3,因为1完全重新分配了对象。您完成所有清理工作,然后完成重新分配/重新初始化对象的所有工作。但是复制分配:

* b = * c.b;

您在代码中使用的变体只是复制数据。

然而,我们要问,你为什么一开始就这样做呢?

在我看来,有两个原因让指针成为班级的成员。第一种是使用b作为不透明指针。如果是这种情况,那么您不需要继续阅读。

然而,更有可能的是你试图将多态性与b一起使用。 IE你有从D继承的D和E类。在这种情况下,你 CAN NOT 使用赋值运算符!以这种方式思考:

B* src_ptr = new D();//pointer to D
B* dest_ptr = new E();//pointer to E
*dest_ptr = *src_ptr;//what happens here?

会发生什么?

好吧,编译器使用赋值运算符看到以下函数调用:

B& = const B&

它只知道B的成员:它无法清理不再使用的E成员,它无法真正从D转换为E.

在这种情况下,通常最好使用情境1而不是尝试辨别子类型,并使用克隆类型运算符。

class B
{
public:
  virtual B* clone() const = 0;
};

B* src_ptr = new E();//pointer to D
B* dest_ptr = new D();//pointer to E, w/e
delete dest_ptr;
dest_ptr = src_ptr->clone();

答案 1 :(得分:2)

这可能取决于示例,但我实际上甚至不知道为什么在堆上分配b。但是,在堆上分配b的原因通知了如何复制/分配它。我认为在堆上分配对象有三个原因,而不是在堆栈上嵌入或分配:

  1. 该对象在多个其他对象之间共享。显然,在这种情况下,共享所有权并不是实际复制的对象,而是指向对象的指针。很可能使用std::shared_ptr<T>来维护对象。
  2. 对象是多态的,支持的类型集是未知的。在这种情况下,实际上不会复制对象,而是使用基类中的自定义虚拟clone()函数进行克隆。由于从中分配的对象的类型不必相同,因此复制构造和赋值实际上都将克隆该对象。该对象可能使用std::unique_ptr<T>或自定义clone_ptr<T>来保存,它会自动处理该类型的适当克隆。
  3. 对象太大而无法嵌入。当然,除非您碰巧实现大对象并为其创建合适的句柄,否则不会发生这种情况。
  4. 在大多数情况下,我实际上会以相同的形式实现赋值运算符:

    T& T::operator=(T other) {
        this->swap(other);
        return *this;
    }
    

    也就是说,对于分配对象的实际副本,代码将利用已经编写的复制构造函数和析构函数(两者实际上可能都是= default ed)加上仅仅交换的swap()方法两个对象之间的资源(假设分配器相等;如果你需要处理非平等分配器的情况,事情会变得更有趣)。实现这样的代码的好处是赋值是强安全的异常。

    回到你的作业方法:在任何情况下我都不会delete一个对象,然后分配替换。此外,我会开始做所有可能失败的操作,将它们放在适当的位置:

    C&安培; C :: operator =(C const&amp; c)    {       std :: unique_ptr tmp(new B(* c.b));       this-&gt; a = c.a;       this-&gt; b = tmp.reset(this-&gt; b);       返回*这个;    }

    请注意,此代码执行自我分配检查。我声称任何实际上仅通过明确防范来自我分配的赋值运算符是异常安全,至少,它不是强烈异常安全的。为基本保证提供案例更难,但在大多数情况下,我已经看到赋值不是基本的异常安全,问题中的代码也不例外:如果分配抛出,this->b包含一个陈旧的指针,它可以不能从另一个指针告诉它(至少需要在nullptr之后和分配之前设置为delete b;

      b->operator=(*(c.b));
      (*b).operator=(*(c.b));
    

    这两项操作是等效的,应该拼写

      *this->b = *c.b;
    

      *b = *c.b;
    

    我更喜欢合格的版本,例如,因为它是有效的,即使b是从模板化基础继承的模板的基类,但我知道大多数人不喜欢它。如果对象的类型恰好是内置类型,则使用operator=()会失败。但是,堆分配对象的简单赋值没有任何意义,因为如果实际做了正确的事情,则应该在堆上分配对象。

答案 2 :(得分:1)

如果您使用方法1,您的赋值运算符甚至不提供基本(例外)保证,因此确定无法使用。

最好的当然是按价值构成。那么你甚至不必编写自己的复制赋值运算符,让编译器为你做这个!

接下来最好,因为出现,您将始终拥有一个有效的b指针,将分配到现有对象中:*b = *c.b;

答案 3 :(得分:1)

a = c.a;
*b = *c.b;

当然,如果b有可能是空指针,代码应该在第二行上进行赋值之前检查它。