安全分配和复制交换习语

时间:2011-05-06 00:25:28

标签: c++ class copy-constructor rule-of-three copy-and-swap

我正在学习c ++并且我最近学习了(这里是堆栈溢出)关于复制和交换的习惯用法,我有一些关于它的问题。所以,假设我使用了一个复制和交换习惯用语,例如:

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

我的问题是,假设我有另一个类将Foo对象作为数据,但没有可能需要自定义复制或赋值的指针或其他资源:

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

现在我有一系列问题:

  1. 上面为Bar类实现的方法和构造函数是否安全?使用Foo的复制和交换后,我确保在分配或复制Bar时不会造成任何伤害?

  2. 在复制构造函数和swap中通过引用传递参数是必需的吗?

  3. 是否正确地说当operator=的参数按值传递时,将为此参数调用复制构造函数以生成对象的临时副本,并且该副本是然后与*this交换?如果我在operator=中通过引用,我会遇到一个大问题,对吗?

  4. 是否存在此习语无法在复制和分配Foo时提供完全安全的情况?

3 个答案:

答案 0 :(得分:8)

您应该尽可能在初始化列表中初始化类的成员。这也将照顾我在评论中告诉你的错误。考虑到这一点,您的代码变为:

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

在整个

中使用相同的习语

注释

如问题评论中所述,Bar的自定义副本构造函数等是不必要的,但我们假设Bar还有其他内容: - )

第二个问题

需要通过引用传递swap,因为两个实例都已更改。

需要通过引用传递复制构造函数,因为如果按值传递,则需要调用复制构造函数

第三个问题

第四个问题

不,但并不总是最有效的做事方式

答案 1 :(得分:5)

  

1 - 上面为Bar类实现的方法和构造函数是否安全?使用Foo的复制和交换后,我确保在分配或复制Bar时不会造成任何伤害?

关于copy-ctor:这总是安全的(全有或全无)。它要么完成(全部),要么抛出异常(没有)。

如果您的类只由一个成员组成(即没有基类),则赋值运算符将与成员类的安全性一样安全。如果您有多个成员,则赋值运算符将不再是“全有或全无”。第二个成员的赋值运算符可以抛出异常,在这种情况下,对象将被分配为“中途”。这意味着您需要在新类中再次实现复制和交换以获得“全部或全部”分配。

然而,在您不会泄漏任何资源的意义上,它仍然是“安全的”。当然,每个成员的状态将是一致的 - 只是新类的状态不一致,因为一个成员被分配而另一个成员没有被分配。

  

2 - 在复制构造函数和swap中通过引用传递参数是必需的吗?

是的,通过引用传递是强制性的。复制构造函数是复制对象的复制构造函数,因此不能按值接受它的参数,因为这意味着必须复制参数。这将导致无限递归。 (拷贝监视器将被调用参数,这意味着要为参数调用copy-ctor,这意味着......)。对于swap,原因是另一个:如果你要按值传递参数,你永远不能使用swap来真正交换两个对象的内容 - 交换的“目标”将是最初传入的对象的副本,这将被立即销毁。

  

3 - 是否正确地说当operator =的参数通过值传递时,为此参数调用复制构造函数以生成对象的临时副本,并且该副本随后与之交换*这个?如果我在operator =中通过引用传递我会有一个大问题,对吗?

是的,没错。然而,通过引用-const获取参数,构造本地副本然后与本地副本交换也是很常见的。 by reference-to-const 方式有一些缺点(它禁用了一些优化)。如果你实现copy-and-swap,你应该通过reference-to-const传递。

  

4 - 在复制和分配Foo时,是否存在这种成语无法提供完全安全的情况?

我不知道。当然,总是可以通过多线程(如果没有正确同步)使事情失败,但这应该是显而易见的。

答案 2 :(得分:2)

这是一个典型的例子,说明跟随成语导致不必要的性能惩罚(过早的悲观化)。这不是你的错。复制和交换习语过于夸张。 是一个很好的习语。但是盲目地应该

注意:您可以在计算机上执行的最昂贵的事情之一是分配和释放内存。在实践中避免这样做是值得的。

注意:在您的示例中,复制和交换习惯用法总是执行一次释放,并且通常(当赋值的rhs是左值时)也会执行一次分配。

观察:size() == rhs.size()时,不需要重新分配或分配。您所要做的就是copy。这很多,速度更快。

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

即。检查您可以先回收资源的情况。如果无法回收资源,请复制并交换(或其他)。

注意:我的评论并不与其他人给出的好答案相矛盾,也没有任何正确性问题。我的评论只是一个重大的性能损失。