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

Posted

技术标签:

【中文标题】在 C++ 中编写复制构造函数和赋值运算符的清单【英文标题】:Checklist for writing copy constructor and assignment operator in C++ 【发布时间】:2010-09-17 21:52:32 【问题描述】:

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

【问题讨论】:

【参考方案1】:

首先确定您确实需要支持副本。大多数情况下并非如此,因此禁用两者是可行的方法。

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

否则,如果您正在编写一个值类,您将回到 Coplien 的正交规范形式的领域。如果您有一个无法简单复制的成员,则需要提供一个复制构造函数、一个析构函数、一个赋值运算符和一个默认构造函数。这条规则可以细化,例如:The Law of The Big Two

我还建议您查看C++ FAQ regarding assignment operators、copy-and-swap idiom 和GOTW。

【讨论】:

我还以为是4的规则呢。 AFAIK,它源自 Jim Coplien 的书,因此得名——顺便说一句,它仅适用于值类。有人称之为四法则。在 C++ FAQ lite 中有(曾经?)关于 /Rule of Big Three/ 的解释(我再也找不到了)。多亏了 RAII,它可以减少到两个。 还可以防止赋值运算符中的自赋值。 @Adrian:如果赋值运算符使用复制和交换,则不需要进一步保护。 其实异常安全足以保证自赋值安全。复制和交换只是提供异常安全性的一种巧妙方法。【参考方案2】:

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

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

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

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

构造函数 复制构造函数 赋值运算符 析构函数

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

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

【讨论】:

“编译器生成的版本适用于大多数情况。” - 取决于你做什么样的编程。即使一切都是智能指针,处理资源跟踪和异常问题,浅拷贝也可能不是你想要的语义。【参考方案3】:

尝试阅读这篇文章。

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

是一个很好的分析赋值运算符

【讨论】:

这通常是我在采访中会问的问题。但是,我看到了一个错误的假设:大多数对象都需要是可复制的。 “精心设计的 C++ 系统中的每个对象都有一个默认构造函数、一个复制构造函数和一个赋值运算符。”这句话适用于基于值的对象,而不是实体。【参考方案4】:

我不知道这里安全异常,但我走这条路。让我们想象它是一个模板化的数组包装器。希望对你有帮助:)

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;
    

【讨论】:

这段代码不是异常安全的。总是在释放前分配! 不,不是。我对异常安全代码没有任何问题,所以我从来没有机会练习它。上面这个只是snipper,通常你使用STL容器。请贴出你的sn-p,我想看看。 我在回复中提供的链接中有很多sn-ps。顺便说一句,阻止自分配的测试现在被认为是反成语(现在更好地理解了异常) “顺便说一句,阻止自赋值的测试现在被视为反成语”链接到解释? 我在另一条消息中提供了链接s -> 请参阅 GOTW 和 FAQ C++ lite。

以上是关于在 C++ 中编写复制构造函数和赋值运算符的清单的主要内容,如果未能解决你的问题,请参考以下文章

c++中拷贝构造函数和赋值运算符重载本质上一样么

C/C++编程笔记:用C++编写赋值运算符,一般什么情况下用?

在面试中,我要求编写构造函数、复制构造函数和赋值运算符

c++ 拷贝构造函数与赋值运算符重载函数的区别是

❥关于C++之类的复制构造函数&赋值运算符

C++:在派生类构造函数中调用基类赋值运算符的错误形式?