在 C++ 中编写复制构造函数和赋值运算符的清单

Checklist for writing copy constructor and assignment operator in C++

提问人:John Smith 提问时间:10/18/2008 最后编辑:Daniel DaranasJohn Smith 更新时间:7/16/2015 访问量:4913

问:

请编写复制构造函数和赋值运算符需要在 C++ 中执行的任务列表,以保持异常安全、避免内存泄漏等。

C++ 复制构造函数 赋值运算符

评论


答:

-2赞 Nazgob 10/18/2008 #1

我不知道这里的例外情况,但我走这条路。让我们想象一下,它是一个模板化的数组包装器。希望对:)有所帮助

Array(const Array& rhs)
    {
        mData = NULL;
        mSize = rhs.size();
        *this = rhs;
    }

    Array& operator=(const Array& rhs)
    {
        if(this == &rhs)
        {
            return *this;
        }

        int len = rhs.size();

        delete[] mData;

        mData = new T[len];

        for(int i = 0; i < len; ++i)
        {
            mData[i] = rhs[i];
        }

        mSize = len;

        return *this;
    }

评论

0赞 Luc Hermitte 10/18/2008
此代码并非异常安全。在发布之前一定要分配!
0赞 Nazgob 10/18/2008
不,不是。我对例外安全代码没有任何问题,所以我从来没有机会练习它。上面的这个只是狙击手,通常你使用 STL 容器。请发布您的片段,我想看看。
0赞 Luc Hermitte 10/18/2008
我在回复中提供的链接中有很多片段。顺便说一句,防止自我分配的测试现在被认为是一种反习语(现在对例外情况有了更好的理解)
0赞 John Smith 10/18/2008
“顺便说一句,防止自我分配的测试现在被认为是反成语”链接到对此的解释?
0赞 Luc Hermitte 10/18/2008
我已经在我的另一条消息中提供了链接 - >请参阅 GOTW 和 FAQ C++ lite。
15赞 Luc Hermitte 10/18/2008 #2

首先,确保你确实需要支持复制。大多数时候情况并非如此,因此禁用两者是要走的路。

有时,您仍然需要在多态层次结构中的类上提供复制,在这种情况下:禁用赋值运算符,编写(受保护?)复制构造函数,并提供虚拟 clone() 函数。

否则,如果你正在编写一个值类,你就回到了 Coplien 的正交规范形式。如果有一个无法简单复制的成员,则需要提供复制构造函数、析构函数、赋值运算符和默认构造函数。这条规则可以细化,例如:两大定律

我还建议查看有关赋值运算符的C++常见问题解答,以及复制和交换习惯用语以及GOTW

评论

0赞 Martin York 10/18/2008
我以为这叫做 4 法则。
0赞 Luc Hermitte 10/19/2008
AFAIK,它起源于 Jim Coplien 的书,因此得名——顺便说一句,它仅适用于值类。有人称其为四法则。在C++ FAQ lite中也有关于/Big Three/的规则的解释(我再也找不到了)。多亏了 RAII,它可以减少到两个。
2赞 Adrian McCarthy 4/8/2010
此外,还可以防止赋值运算符中的自我赋值。
3赞 Steve Jessop 6/15/2011
@Adrian:如果赋值运算符使用复制和交换,则不需要进一步的保护。
0赞 Luc Hermitte 6/17/2011
实际上,异常安全足以保证自分配安全。复制和交换只是提供异常安全性的一种巧妙方法。
4赞 Martin York 10/19/2008 #3

编译器生成的版本在大多数情况下都可以工作。

当对象包含 RAW 指针(没有 RAW 指针的参数)时,您需要更努力地思考这个问题。所以你有一个RAW指针,第二个问题是你是否拥有这个指针(它是否被你删除了)?如果是这样,那么您将需要应用 4 规则。

拥有超过 1 个 RAW 指针变得越来越难以正确完成(复杂性的增加也不是线性的 [但这是观察性的,我没有真正的统计数据来支持这一说法])。因此,如果您有 1 个以上的 RAW 指针,请考虑将每个指针包装在自己的类中(某种形式的智能指针)。

规则 4:如果对象是 RAW 指针的所有者,则需要定义以下 4 个成员,以确保正确处理内存管理:

  • 构造 函数
  • Copy 构造函数
  • 赋值运算符
  • 破坏者

如何定义这些将取决于具体情况。但需要注意的事项:

  • 默认构造:将指针设置为 NULL
  • 复制构造函数:使用 Copy and Swap 概念提供给“强异常保证”
  • 赋值运算符:检查对自身的赋值
  • 析构函数:防止异常从析构函数传播出来。

评论

0赞 Steve Jessop 10/21/2008
“编译器生成的版本适用于大多数情况”——取决于你做的编程类型。即使一切都是智能指针,处理资源跟踪和异常问题,浅拷贝也可能不是你想要的语义。
1赞 ugasoft 11/6/2008 #4

试着读这个。

http://www.icu-project.org/docs/papers/cpp_report/the_anatomy_of_the_assignment_operator.html

是很好的分析赋值算子

评论

0赞 Luc Hermitte 11/7/2008
这通常是我在面试中会问的问题。然而,我看到了一个错误的假设:大多数对象都需要可复制。“精心设计的 C++ 系统中的每个对象都有一个默认构造函数、一个复制构造函数和一个赋值运算符。这句话适用于基于值的对象,而不是实体。