减少 operator= 和拷贝构造函数之间的代码重复

22

我有一个类,需要一个非默认的复制构造函数和赋值运算符(它包含指针列表)。有没有一种通用的方法可以减少复制构造函数和赋值运算符之间的代码重复?


2
我认为你应该能够在operator=函数内调用复制构造函数。 - San Jacinto
一些答案在这里:https://dev59.com/AnM_5IYBdhLWcg3wQQtd - Johannes Schaub - litb
4个回答

20

没有一种"通用的方法"可以编写适用于所有情况的自定义复制构造函数和赋值运算符。但有一种习惯用法称为"拷贝并交换(copy-&-swap)":

 class myclass
 {
    ...
 public:
    myclass(myclass const&);

    void swap(myclass & with);

    myclass& operator=(myclass copy) {
        this->swap(copy);
        return *this;
    }

    ...
};

在许多情况下这很有用(但并非所有情况)。有时您可以做得更好。如果向量或字符串足够大,它们可能具有更好的赋值方式,可以重用已分配的存储空间。


2
+1 - 对于复制/交换(copy-swap)的简洁而好的总结,我认为非常不错。同时重复使用存储空间的想法也很好。 - Johannes Schaub - litb
3
你可能需要指出这里的细微差别,即你的operator=和更标准的const myclass& operator=(const myclass& other)之间的区别。 - Bill
1
swap() 应该标记为 nothrow。Bills 的评论也是如此。更多的解释会更好。 - Martin York
这里的 this->swap(copy) 有什么特别之处,还是 swap(copy) 也可以工作? - kevinarpe
1
@kevinarpe:没什么特别的。swap(copy)也可以。有时我会写this->来更明确地表示它是数据成员或成员函数。 - sellibitze
显示剩余4条评论

17

将常见代码提取到一个私有成员函数中。以下是一个简单(有点勉强)的示例:

#include <iostream>

class Test
{
public:
  Test(const char* n)
  {
    name = new char[20];
    strcpy(name, n);
  }

  ~Test()
  {
    delete[] name;
  }

  // Copy constructor
  Test(const Test& t)
  {
    std::cout << "In copy constructor.\n";
    MakeDeepCopy(t);
  }

  // Assignment operator
  const Test& operator=(const Test& t)
  {
    std::cout << "In assignment operator.\n";
    MakeDeepCopy(t);
  }

  const char* get_name() const { return name; }

private:
  // Common function where the actual copying happens.
  void MakeDeepCopy(const Test& t)
  {        
    strcpy(name, t.name);
  }

private:
  char* name;
};

int
main()
{
  Test t("vijay");
  Test t2(t); // Calls copy constructor.
  Test t3(""); 
  t3 = t2; // Calls the assignment operator.

  std::cout << t.get_name() << ", " << t2.get_name() << ", " << t3.get_name() << '\n';

  return 0;
}

3
内存泄漏!MakeDeepCopy忽略了指针可能已经指向分配的内存空间的情况。 - sellibitze
4
很遗憾,拷贝构造函数不能使用成员初始化器,这样... - xtofl
2
注意,按写法 t2 = t2 赋值运算符是无法正常工作的。(如果你想对它们排序,这一点很重要。) - dave4420
2
@Vijay:但是现在你忘记为复制构造函数初始化“name”,这在某种程度上证明了我的观点。当涉及显式资源管理时,复制构造和复制赋值执行不同的操作。重用的可能性很小。 - sellibitze
2
先生,我不太明白,但是您的operator=没有返回任何内容,对吗? - Alcott
显示剩余3条评论

8
My &My::operator = (My temp)  // thanks, sellibitze
{
    swap (*this, temp);
    return *this;
}

并实现一个专门的std::swap<> (My &, My &)


1
这不是 copy-&-swap 应该看起来的样子。你明确地创建了一个可能被省略的拷贝。这里有一篇很棒的文章讲解这个问题:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ - sellibitze
3
正如 Dave 注意到的那样,alexandrescu 已经在6年前发现了这个问题:http://www.ddj.com/cpp/184403855。这篇文章很有趣! - Johannes Schaub - litb
1
顺便说一下,发帖人也叫戴夫。对于混淆造成的困扰,我感到很抱歉 - 我指的是cpp-next的Dave Abrahams :) - Johannes Schaub - litb
那篇Alexandrescu的文章真的很有用。 - Dan Hook

3
正如许多发帖者已经指出的那样,operator= 创建一个具有复制构造函数的新对象,然后使用交换是常用的技术,用于不必在 operator= 中复制代码。
话虽如此,我想指出这种技术的一些优点和缺点,以帮助您决定是否适合使用。
优点 - 异常安全
如果您的对象具有可能引起异常的资源需求,并且假设交换不会引发异常,则此技术提供了异常安全的强保证(要么被分配给的对象已经取得了另一个对象的值,要么它没有改变)。
缺点 - 资源占用
这种技术的问题是,需要在释放旧对象之前创建一个完整的新对象。如果您的对象需要大量资源,这可能是一个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接