在复制操作员方面或单独实施copy-ctor?

时间:2010-11-08 07:37:10

标签: c++ copy-assignment

这不是Implementing the copy constructor in terms of operator=的重复,而是一个更具体的问题。 (或者我喜欢思考。)

简介

鉴于这样的(假设的)课程:

struct FooBar {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  NonCopyable implementation_detail; // cannot and must not be copied

  // ...
};

我们无法通过默认生成的函数复制它,因为您既不能复制构造也不能复制NonCopyable对象。但是,该对象的这一部分是我们实际上对复制不感兴趣的实现细节。

对于编写交换函数也没有任何意义,因为交换函数可以复制std :: swap的作用(减去NonCopyable)。

因此,如果我们想要复制这些对象,我们就会自己实现copy-ctor和copy-operator。只需指定其他成员即可轻松完成。

问题

如果我们需要实现copy ctor和operator,我们是应该根据copy运算符实现copy ctor,还是应该用初始化列表“复制”代码?

即,给定:

FooBar& operator=(FooBar const& rhs) {
  // no self assignment check necessary
  id = rhs.id;
  valX = rhs.valX;
  valZ = rhs.valZ;
  valN = rhs.valN;
  flag = rhs.flag;
  // don't copy implementation_detail
  return *this;
}

我们应该写一个)

FooBar(FooBar const& rhs) {
  *this = rhs;
}

或b)

FooBar(FooBar const& rhs)
: id(rhs.id)
, valX(rhs.valX)
, valZ(rhs.valZ)
, valN(rhs.valN)
, flag(rhs.flag)
// don't copy implementation_detail
{ }

答案的可能方面是性能与可维护性和可读性。

5 个答案:

答案 0 :(得分:5)

通常你会根据复制构造函数(@Roger Pate的版本)实现赋值运算符:

FooBar& operator=(FooBar copy) { swap(*this, copy); return *this; }
friend void swap(FooBar &a, FooBar &b) {/*...*/}

这需要提供一个swap函数来交换相关成员(除了implementation_detail之外的所有成员)。

如果swap没有抛出此方法,则保证对象不会处于某种不一致状态(仅指定部分成员)。

但是在你的情况下,既然复制构造函数也没有赋值运算符可以在赋值运算符(a)方面抛出实现拷贝构造函数也很好并且更易于维护,那么在两个地方都有几乎相同的代码(b)。

答案 1 :(得分:1)

一般来说,我更喜欢b)而不是a)因为它明确地避免了成员的任何默认构造。对于不是考虑因素的int,double等,但它可以用于具有昂贵操作或副作用的成员。如果您在添加和删除成员时不必考虑此潜在成本/问题,则更易于维护。 Initialiser列表还支持引用和非default-constructable元素。

或者,您可以拥有非“实现细节”成员的子结构,并让编译器生成复制代码,如下所示:

struct X 
{
    struct I
    {
        int x_;
        int y_;
    } i_;
    int z_;

    X() { }

    X(const X& rhs)
      : i_(rhs.i_), z_(0) // implementation not copied
    { }

    X& operator=(const X& rhs)
    {
        i_ = rhs.i_;
        return *this;
    } 
};

答案 2 :(得分:1)

如果您对复制std :: swap感到困扰,为什么不将实现细节以外的所有内容放入结构中?

struct FooBarCore {
  long id;
  double valX;
  double valZ;
  long   valN;
  bool   flag; 
  // ...
};

struct FooBar {
  FooBarCore core_;
  NonCopyable implementation_detail; // cannot and must not be copied
};

然后你可以在FooBar的复制函数中使用std :: swap作为这个结构。

FooBar& operator=(const FooBar &src) {
  FooBarCore temp(src.core_)
  swap(temp,*this.core_);
  return *this;
}

答案 3 :(得分:1)

好的,另一次尝试,基于我对this answer的评论。

将implementation_detail包装在可复制的类中:

class ImplementationDetail
{
public:
  ImplementationDetail() {}
  ImplementationDetail(const ImplementationDetail&) {}
  ImplementationDetail& operator=(const ImplementationDetail&) {}

public: // To make the example short
  Uncopyable implementation_detail;
};

并在你的FooBar中使用这个类。为Foobar默认生成的复制构造函数和复制赋值运算符将正常工作。

也许它甚至可能来自Uncopyable,因此您不会在代码中获得implementation_detail.implementation_detail。或者,如果您将代码控制到implementation_detail类,只需添加空的Copy Constructor并清空Assignment Operator。

答案 4 :(得分:0)

如果复制构造函数不需要复制implementation_detail并且仍然是正确的(我怀疑后者,但我们暂时假设它),implementation_detail是多余的。

所以解决方案似乎是:使implementation_detail成为静态并依赖于默认生成的Copy Constructor和Assignment Operator。