gpt4 book ai didi

c++ - 什么是三法则?

转载 作者:太空宇宙 更新时间:2023-11-04 12:56:35 24 4
gpt4 key购买 nike

  • 复制对象是什么意思?
  • 什么是副本构造函数和副本分配运算符?
  • 我什么时候需要自己声明它们?
  • 如何防止对象被复制?
  • 最佳答案

    介绍
    C++使用值语义处理用户定义类型的变量。
    这意味着对象会在各种上下文中隐式复制,
    我们应该了解“复制对象”的实际含义。
    让我们考虑一个简单的示例:

    class person
    {
    std::string name;
    int age;

    public:

    person(const std::string& name, int age) : name(name), age(age)
    {
    }
    };

    int main()
    {
    person a("Bjarne Stroustrup", 60);
    person b(a); // What happens here?
    b = a; // And here?
    }
    (如果您对 name(name), age(age)部分感到困惑,
    这称为 member initializer list。)
    特殊成员功能
    复制 person对象是什么意思? main函数显示了两种不同的复制方案。
    初始化 person b(a);由复制构造函数执行。
    它的工作是根据现有对象的状态构造一个新对象。
    分配 b = a由副本分配运算符执行。
    它的工作通常比较复杂,
    因为目标对象已经处于某种有效状态,需要处理。
    由于我们自己都没有声明复制构造函数或赋值运算符(也没有析构函数),
    这些是为我们隐式定义的。从标准引用:

    The [...] copy constructor and copy assignment operator, [...] and destructor are special member functions.[ Note: The implementation will implicitly declare these member functionsfor some class types when the program does not explicitly declare them.The implementation will implicitly define them if they are used. [...] end note ][n3126.pdf section 12 §1]


    默认情况下,复制对象意味着复制其成员:

    The implicitly-defined copy constructor for a non-union class X performs a memberwise copy of its subobjects.[n3126.pdf section 12.8 §16]


    The implicitly-defined copy assignment operator for a non-union class X performs memberwise copy assignmentof its subobjects.[n3126.pdf section 12.8 §30]


    隐式定义 person的隐式定义的特殊成员函数如下所示:
    // 1. copy constructor
    person(const person& that) : name(that.name), age(that.age)
    {
    }

    // 2. copy assignment operator
    person& operator=(const person& that)
    {
    name = that.name;
    age = that.age;
    return *this;
    }

    // 3. destructor
    ~person()
    {
    }
    在这种情况下,按成员复制正是我们想要的:
    复制了 nameage,因此我们获得了一个独立的 person对象。
    隐式定义的析构函数始终为空。
    在这种情况下,这也很好,因为我们没有在构造函数中获取任何资源。
    person析构函数完成之后,隐式调用成员的析构函数:

    After executing the body of the destructor and destroying any automatic objects allocated within the body,a destructor for class X calls the destructors for X's direct [...] members[n3126.pdf 12.4 §6]


    管理资源
    那么什么时候应该显式声明那些特殊的成员函数呢?
    当我们的类(class)管理资源时,即
    当类的对象负责该资源时。
    这通常意味着在构造函数中获取资源
    (或传递到构造函数中)并在析构函数中释放。
    让我们回到过去的标准C++。
    没有 std::string这样的东西,程序员爱上了指针。 person类可能看起来像这样:
    class person
    {
    char* name;
    int age;

    public:

    // the constructor acquires a resource:
    // in this case, dynamic memory obtained via new[]
    person(const char* the_name, int the_age)
    {
    name = new char[strlen(the_name) + 1];
    strcpy(name, the_name);
    age = the_age;
    }

    // the destructor must release this resource via delete[]
    ~person()
    {
    delete[] name;
    }
    };
    即使到了今天,人们仍然以这种风格编写类(class)并陷入困境:
    “我把一个人推向一个 vector ,现在我得到了疯狂的记忆错误!”
    请记住,默认情况下,复制对象意味着复制其成员,
    但是复制 name成员仅复制一个指针,而不复制它指向的字符数组!
    这有几个令人不愉快的影响:
  • 通过a可以观察到b的更改。
  • 一旦b被销毁,a.name是一个悬空的指针。
  • 如果a被破坏,则删除悬空指针将产生undefined behavior
  • 由于分配未考虑分配前name指向的内容,
    迟早您到处都会出现内存泄漏。

  • 明确定义
    由于逐成员复制没有达到预期的效果,因此我们必须显式定义复制构造函数和复制赋值运算符以制作字符数组的深拷贝:
    // 1. copy constructor
    person(const person& that)
    {
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
    }

    // 2. copy assignment operator
    person& operator=(const person& that)
    {
    if (this != &that)
    {
    delete[] name;
    // This is a dangerous point in the flow of execution!
    // We have temporarily invalidated the class invariants,
    // and the next statement might throw an exception,
    // leaving the object in an invalid state :(
    name = new char[strlen(that.name) + 1];
    strcpy(name, that.name);
    age = that.age;
    }
    return *this;
    }
    注意初始化和赋值之间的区别:
    我们必须在分配给 name之前拆除旧状态,以防止内存泄漏。
    另外,我们必须防止 x = x形式的自我分配。
    如果没有该检查, delete[] name将删除包含源字符串的数组,
    因为当您编写 x = x时, this->namethat.name都包含相同的指针。
    异常安全
    不幸的是,如果 new char[...]由于内存耗尽而引发异常,则该解决方案将失败。
    一种可能的解决方案是引入局部变量并对语句重新排序:
    // 2. copy assignment operator
    person& operator=(const person& that)
    {
    char* local_name = new char[strlen(that.name) + 1];
    // If the above statement throws,
    // the object is still in the same state as before.
    // None of the following statements will throw an exception :)
    strcpy(local_name, that.name);
    delete[] name;
    name = local_name;
    age = that.age;
    return *this;
    }
    这也可以在没有明确检查的情况下进行自我分配。
    copy-and-swap idiom是解决此问题的更强大的解决方案,
    但是我在这里不会详细讨论异常安全性。
    我只提到了异常(exception),以说明以下几点: 编写用于管理资源的类很困难。
    不可复制的资源
    某些资源不能或不应被复制,例如文件句柄或互斥体。
    在这种情况下,只需将复制构造函数和复制赋值运算符声明为 private而不给出定义:
    private:

    person(const person& that);
    person& operator=(const person& that);
    或者,您可以继承 boost::noncopyable或将其声明为已删除(在C++ 11及更高版本中):
    person(const person& that) = delete;
    person& operator=(const person& that) = delete;
    三法则
    有时您需要实现一个管理资源的类。
    (永远不要在一个类中管理多个资源,
    这只会导致疼痛。)
    在这种情况下,请记住三个的 规则:

    If you need to explicitly declare either the destructor,copy constructor or copy assignment operator yourself,you probably need to explicitly declare all three of them.


    (不幸的是,C++标准或我所知道的任何编译器都没有执行此“规则”。)
    五法则
    从C++ 11开始,对象具有2个额外的特殊成员函数:move构造函数和move赋值。五个州的规则也可以实现这些功能。
    带有签名的示例:
    class person
    {
    std::string name;
    int age;

    public:
    person(const std::string& name, int age); // Ctor
    person(const person &) = default; // 1/5: Copy Ctor
    person(person &&) noexcept = default; // 4/5: Move Ctor
    person& operator=(const person &) = default; // 2/5: Copy Assignment
    person& operator=(person &&) noexcept = default; // 5/5: Move Assignment
    ~person() noexcept = default; // 3/5: Dtor
    };
    零法则
    3/5的规则也称为0/3/5的规则。规则的零部分表示在创建类时不允许编写任何特殊成员函数。
    忠告
    大多数时候,您不需要自己管理资源,
    因为现有的类(例如std::string)已经为您完成了。
    只是使用std::string成员比较简单的代码
    使用char*进行复杂且容易出错的选择,您应该被说服。
    只要您远离原始指针成员,三个规则就不太可能涉及您自己的代码。

    关于c++ - 什么是三法则?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46282900/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com