安全分配和复制交换习惯用法

发布于 2024-11-05 02:43:24 字数 1436 浏览 1 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

疧_╮線 2024-11-12 02:43:24

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

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的自定义复制构造函数等是不必要的,但我们假设< code>Bar 还有其他东西:-)

第二个问题

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

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

第三个问题

第四个问题

否,但这并不总是最有效的处理方式

As much as possible, you should initialize the members of your class in the initializer list. That will also take care of the bug I told you about in the comment. With this in mind, your code becomes:

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];}
};

and

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); }
}

which uses the same idiom throughout

note

as mentioned in a comment on the question, Bar's custom copy constructors etc. are unnecessary, but we'll assume Bar has other things as well :-)

second question

Passing by reference to swap is needed because both instances are changed.

Passing by reference to the copy constructor is needed because if passing by value, you'd need to call the copy constructor

third question

yes

fourth question

no, but it is not always the most efficient way of doing things

椒妓 2024-11-12 02:43:24

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

关于复制者:这总是安全的(全有或全无)。它要么完成(全部),要么抛出异常(什么也没有)。

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

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

2 - 在复制构造函数和交换中通过引用传递参数是强制的吗?

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

3 - 当operator=的参数按值传递时,会调用该参数的复制构造函数来生成对象的临时副本,然后将这个副本与*这?如果我在operator=中通过引用传递,我会有一个大问题,对吗?

是的,没错。然而,通过引用 const 获取参数、构造本地副本,然后与本地副本进行交换也很常见。然而,通过引用常量的方式有一些缺点(它禁用了一些优化)。如果您没有实现复制和交换,那么您可能应该通过引用const来传递。

4 - 在某些情况下,该习惯用法无法在复制和分配 Foo 时提供完全的安全性吗?

据我所知,没有一个。当然,多线程总是会让事情失败(如果没有正确同步),但这应该是显而易见的。

1 - Are the methods and constructors as implemented above for the Bar class safe? Having used the copy-and-swap for Foo make me sure that no harm can be done when assigning or copying Bar?

Regarding the copy-ctor: that's always safe (all-or-nothing). It either completes (all), or it throws an exception (nothing).

If your class is only made up of one member (i.e. no base classes as well), the assignment operator will be just as safe as that of the member's class. If you have more then one member, the assignment operator will no longer be "all or nothing". The second member's assignment operator could throw an exception, and in that case the object will be assigned "half way". That means you need to implement copy-and-swap again in the new class to get "all or nothing" assignment.

However it will still be "safe", in the sense that you won't leak any resources. And of course the state of each member individually will be consistent - just the state of the new class won't be consistent, because one member was assigned and the other was not.

2 - Passing the argument by reference in the copy constructor and in swap is mandatory?

Yes, passing by reference is mandatory. The copy constructor is the one that copies objects, so it cannot take it's argument by value, since that would mean the argument has to be copied. This would lead to infinite recursion. (The copy-ctor would be called for the argument, which would mean calling the copy-ctor for the argument, which would mean ...). For swap, the reason is another: if you were to pass the argument by value, you could never use swap to really exchange the contents of two objects - the "target" of the swap would be a copy of the object originally passed in, which would be destroyed immediately.

3 - Is it right to say that when the argument of operator= is passed by value, the copy constructor is called for this argument to generate a temporary copy of the object, and that it is this copy which is then swapped with *this? If I passed by reference in operator= I would have a big problem, right?

Yes, that's right. However it's also quite common to take the argument by reference-to-const, construct a local copy and then swap with the local copy. The by reference-to-const way has some drawbacks however (it disables some optimizations). If you're not implementing copy-and-swap though, you should probably pass by reference-to-const.

4 - Are there situations in which this idiom fails to provide complete safety in copying and assigning Foo?

None that I know of. Of course one can always make things fail with multi-threading (if not properly synchronized), but that should be obvious.

得不到的就毁灭 2024-11-12 02:43:24

这是一个典型的例子,说明遵循习惯用法会导致不必要的性能损失(过早悲观)。这不是你的错。复制和交换的说法被过度炒作了。这是一个很好的习语。但不应该盲目遵循。

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

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

观察:size() == rhs.size()时,不需要进行释放或分配。您所要做的就是复制一份。这快得多,快得多。

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

即首先检查是否可以回收资源。如果您无法回收资源,则可以进行复制和交换(或其他方式)。

注意:我的评论与其他人给出的好的答案并不矛盾,也不存在任何正确性问题。我的评论只是关于显着的性能损失。

This is a classic example of where following an idiom leads to unnecessary performance penalties (premature pessimization). This isn't your fault. The copy-and-swap idiom is way over-hyped. It is a good idiom. But it should not be followed blindly.

Note: One of the most expensive things you can do on a computer is allocate and deallocate memory. It is worthwhile to avoid doing so when practical.

Note: In your example, the copy-and-swap idiom always performs one deallocation, and often (when the rhs of the assignment is an lvalue) one allocation as well.

Observation: When size() == rhs.size(), no deallocation or allocation need be done. All you have to do is a copy. This is much, much faster.

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

I.e. check for the case where you can recycle resources first. Then copy-and-swap (or whatever) if you can't recycle your resources.

Note: My comments do not contradict the good answers given by others, nor any correctness issues. My comment is only about a significant performance penalty.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文