第二章、构造析构赋值运算

    xiaoxiao2025-01-08  14

    Constructors,Destructors,and Assignment Operators

    条款05、了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls)条款06、若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of compiler-generated functions you do not want)条款07、为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes)条款08、别让异常逃离析构函数(Prevent exceptions from leaving destructors)条款09、绝不在析构和构造函数中调用virtual函数(Never call virtual functions durig construction or destruction)条款10、令operator=返回一个reference to *this(Have assignment operators a reference to *this)条款11、在operator=中处理“自我赋值”(Handle assignment to self in operator=)条款12、复制对象时勿忘每一个成分(Copy all parts of an object)

    条款05、了解C++默默编写并调用哪些函数(Know what functions C++ silently writes and calls)

    对于一个class,如果你自己没有声明,则编译器就会为这个类声明一个copy构造函数、一个copy assignment操作符和一个析构函数。如果你没有声明任何构造函数,编译器还会为你声明一个default构造函数。所有这些函数都是public且inline的。不过唯有这些函数被调用了,它们才会被编译器创造出来。编译器产出的析构函数是non-virtual的,除非这个class的base class自身声明有virtual析构函数。对于编译器生成的copy构造函数和copy assignment操作符,它们只是单纯的将来源对象中的每一个non-static成员拷贝到目标对象中。如果你声明了自己的构造函数,编译器则不会再生成default构造函数,除非你明确要求编译器生成。如果class中含有C++中默认无法拷贝的类型(例如被const修饰的类型或reference),则编译器会拒绝为该class生成copy assignment操作符。如果base class中含有private属性的copy assignment操作符,那么在derived class中,编译器也会拒绝为该class生成copy assignment操作符。

    条款06、若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of compiler-generated functions you do not want)

    将对应得成员函数声明为private并且不予实现,可以阻止编译器生成默认版本,同时也可以阻止客户调用。

    条款07、为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base classes)

    当base class中包含virtual析构函数时,销毁其derived class对象时会销毁所有的base class成分和derived class成分。如果一个class不意图成为一个基类,则它不应该包含任何virtual函数。对于virtual函数,需要在运行期才能决定其调用。virtual函数的信息由vptr(virtual table pointer)指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl。vptr会储存在对象的内存中,virtual函数的存在会增加对象体积。当class中存在virtual时,就不再可能把它传递至(或接受自)其他语言所写的函数,除非你明确补偿vptr,所以就不再具有移植性。建议当一个class中至少包含一个virtual函数时,才为其声明virtual析构函数。pure virtual函数的存在会导致abstract class - 也就是不能被实体化的class。你不能为这种类型的class创建对象。

    对于那些没有纯虚函数,又希望这个class是抽象类的时候,可以为其声明一个pure virtual析构函数。但是你仍要为这个pure virtual虚构函数提供一份定义。 class AWOV{ public: virtual ~AWOV() = 0; } AWOV::~AWOV(){}; 析构函数的运作方式是,最深层派生的那个class的析构函数最先被调用,然后是每一个base class的析构函数被调用。编译器会在AWOV的derived class的析构函数中创建一个>对~ AWOV的调用动作,所以需要提供一份~AWOV的定义。

    条款08、别让异常逃离析构函数(Prevent exceptions from leaving destructors)

    如果程序在析构函数中抛出异常,容易导致程序过早结束或出现未定义的行为。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下它们或者结束程序。

    对于吞下异常(catch)而言:将异常吞掉并不好,因为它压制了某些动作失败的重要信息,不过即使这样,吞下异常仍然比由于草率的结束程序或由不明确的行为带来的风险要好。 对于结束程序而言:通常由调用std::abort完成,强制程序结束可以防止异常离开析构函数。

    如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数执行该操作。

    条款09、绝不在析构和构造函数中调用virtual函数(Never call virtual functions durig construction or destruction)

    在derived class的base class构造期间,对象的类型时base class而不是derived class。不只virtual函数会被编译器解析至base class,如使用运行期信息,也会把对象视为base class,所以对象在derived class构造函数执行前不会成为一个derived class对象。对于析构函数,一旦derived class析构函数开始执行,对象内的derived class成分就会成为未定义的值,当进入base class后对象就成为一个base class对象,所以在析构函数中也不应调用virtual函数。

    条款10、令operator=返回一个reference to *this(Have assignment operators a reference to *this)

    令operator=返回一个reference to *this可以实现“连锁赋值”操作。该协议无强制性,但被所有内置类型和标准库类型共同遵守。

    条款11、在operator=中处理“自我赋值”(Handle assignment to self in operator=)

    自我赋值会发生在对象被赋值给自己时。为了防止在自我赋值时出现资源的异常释放,传统的做法是在operator=的最前面增加”证同测试“的操作。

    Widget& Widget::operator=(const Widget& rhs) { if (&rhs == this) return *this;//证同测试 正常赋值操作; }

    使用copy and swap技术,可以保证异常安全和自我赋值安全。

    Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); swap(temp); return *this; }

    对于任何函数,如果其操作一个以上的对象,且其中多个对象是同一对象时,应保证其行为依然正确。

    条款12、复制对象时勿忘每一个成分(Copy all parts of an object)

    编译器版本的copying函数,会将被拷贝对象的所有成员都做一份拷贝。在自定义的copying函数中,如果你忘了对某些成分做拷贝,编译器一般不会提醒你,即使在在高警告级别中。如果你在class增加了成员,你需要对每一个copying函数进行修改。在derived class的copying函数中,需要复制其每一个base class成分,如果base class中存在private的成员,则需要调用base class的copying函数对base class成分赋值。不要尝试用某个copying函数实现另一个copying函数。应该将共同机能放入第三个函数,并由两个copying函数共同调用。
    最新回复(0)