4

这不是在 operator= 方面实现复制构造函数的重复,而是一个更具体的问题。(或者我喜欢这样想。)

介绍

给定一个像这样的(假设的)类:

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

  // ...
};

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

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

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

问题

如果我们需要实现copy ctor和operator,我们应该根据copy operator来实现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;
}

我们应该写a)

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
{ }

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

4

5 回答 5

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)具有几乎相同的代码更易于维护。

于 2010-11-08T07:40:52.343 回答
1

一般来说,我更喜欢 b) 而不是 a),因为它明确避免了任何默认的成员构造。对于整数、双精度数等,这不是一个考虑因素,但它可能适用于具有昂贵操作或副作用的成员。如果您在添加和删除成员时不必考虑这个潜在的成本/问题,那么它更易于维护。Initialiser 列表还支持引用和非默认可构造元素。

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

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;
    } 
};
于 2010-11-08T08:57:59.670 回答
1

好的,根据我对这个答案的评论,再试一次。

将 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。

于 2010-11-08T11:37:16.173 回答
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
};

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

FooBar& operator=(const FooBar &src) {
  FooBarCore temp(src.core_)
  swap(temp,*this.core_);
  return *this;
}
于 2010-11-08T10:21:01.840 回答
0

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

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

于 2010-11-08T08:40:36.613 回答