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

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

【问题讨论】:

    标签: c++ copy-constructor assignment-operator


    【解决方案1】:

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

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

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

    我还建议您查看C++ FAQ regarding assignment operatorscopy-and-swap idiomGOTW

    【讨论】:

    • 我还以为是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。
    猜你喜欢
    • 2020-06-13
    • 2011-07-19
    • 1970-01-01
    • 2022-01-03
    • 2013-09-28
    • 2019-10-12
    • 2011-06-09
    • 1970-01-01
    相关资源
    最近更新 更多