Effective C++ <二>:构造,析构,赋值运算

本文探讨了C++中构造函数、析构函数及赋值操作符的默认行为与自定义实现,强调了理解编译器自动生成函数的重要性,并介绍了如何正确地管理对象资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果这些函数出错的化,将会遍及整个class

条款05:了解C++默默编写并调用哪些函数

真讨厌,编译器会帮你添加上一些函数
如果你写下

class Empty{};

就好像如下

class Empty
{
    public:
        Empty() {...};  //default构造函数
        Empty(const Empty& rhs) {...} //copy构造函数
        ~Empty() {...}  //析构函数
                        //virtual..
        Empty& operator = (const Empty& rhs){...}  //copy assign操作符

}

如果你声明了其中一个(default构造函数,拷贝构造函数,copy assign构造函数)构造函数,则编译器不会为你创建default构造函数;但如果你没声明拷贝构造函数,copy assign构造函数,但调用他们的时候,编译器会为你创建他们。
上面的意思就是说

只要你自己写了三个构造函数当中的任何一个,编译器都不会帮你产生default构造函数了,如果需要使用/调用,你需要自己去写一个;但其他的(拷贝构造函数,copy assign构造函数)只要你没写,编译器就会帮你生成一个(当然是在你调用的时候)

条款6 若不想使用编译器自动生成的函数,就该明确拒绝

一些member成员函数和friend函数还是可以调用private函数,“将成员函数声明为private函数,而不去实现它”是常用的伎俩。
这里举一个例子 class HomeForSale{…}, 这个类不想被赋值(=)或拷贝(HomeForSale a(b)),
因此可以这样

class HomeForSale
{
    public:
        ...
    private:
        HomeForSale(const HomeForSale&);
        HomeForSale operator = (const HomeForSale&);
};
//但是这个要实现构造函数

可是一些member成员函数和friend函数还是可以调用private函数的,所以说,编译阶段不会出错,链接阶段会出错,可以使用下面方法将错误提前到编译阶段

class Uncopyable
{
    protected:
        Uncopyable();
        ~Uncopyable();
    private:
        Uncopyable(const Uncopyable&);
        Uncopyable& operator = (const Uncopyable&);
};

class HomeForSale : private Uncopyable //class不再声明copy构造函数或copy assign操作符
{
    ...
}

因为HomeForSale内有memeber函数或友元函数想要尝试拷贝HomeForSale对象,编译器便会尝试生成一个copy构造函数或copy assign操作符,则会去调用其base类的copy构造函数或copy assign操作符,而base类的是private,因此编译会失败。
另外boost有个可使用版本boost::noncopyable。???查下使用方法

条款7:为多态基类声明为virtual析构函数

  1. 当drived class对象由一个base class指针被删除,而该base class带有一个non-virtual析构函数,其结果未定义,实际执行的时候通常发生的是对象的drived成分没被销毁,因此解决办法给base class 一个virtual析构函数,此后删除drived class对象就会如你想要的那般

  2. 但是当class不企图被当作base class,令其西沟函数为virtual往往是个馊主意,为什么呢?因为C++析构函数的实现是由vptr(virtual table pointer)指向一个vtbl(virtual table),每一个带有virtual函数的class都有一个相应的vtbl,这样class的内存空间会加大!!

  3. 即使class完全不带virtual函数,被”non-virtual析构函数问题”给咬伤还是有可能的,如std::string就不含任何virtual函数,如下面错误

class SpecialString : public std::string{...}

这样在delete的时候就可能出错,包括所有的STL容器如vector,list,set,tr1::unordered_map等等,所以说不要企图继承一个标准容器,或是带有non-virtual析构函数的class

  1. 也可以创建一个带有pure virtual析构函数,如下
class AWOV
{
    public:
        virtual ~AWOV() = 0;  //声明pure virtual析构函数
};

但是必须为pure virtual函数提供一份定义

AWOV()::~AWOV(){}

因为析构函数运作方式:先调用drived的析构函数,再调用base class的析构函数,如果没有定义的话,链接器会发生抱怨的。

条款8 别让异常逃离析构函数

。。。

条款9 绝不在构造和析构过程中调用virtual函数

base class构造期间virtual函数绝不会下降到drived class阶层,或另一种说法base class构造期间,virtual函数就不是virtual函数,对于析构函数也如此。至于原因理由看书吧,
以后需要特别注意class的创建和销毁的时机,是否已经创建完毕,是否已经正确销毁,很重要。那如何转换呢?看一个例子
代码更改前

class Transaction
{
    public:
        Transaction();
        virtual void log() const = 0;
};

Transaction::Transaction()
{
    ... 
    log();
}

class BuyTransaction : public Transaction
{
    public:
        virtual void log() const;                                                                                                     
}
class Transaction
{
    public:
        explicit Transaction(const string& loginfo);
        void log(const string& loginfo) const;
};

Transaction::Transaction(const string& info)
{
    ... 
    log(info);
}

class BuyTransaction : public Transaction
{
    public:
        BuyTransaction(para)
            : Transaction(createLog(para))
        {...}
    private:
        static string createLog(para);
}

条款10 令operator=返回一个reference to *this

一般这样做

class Widget
{
    public:
        Widget& operator = (const Widget& rhs)
        {   
            ... 
            return *this;
        }   
};

这个不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,比如operator +=,等。
这个不遵循还是可以编译通过的,但是string, vector,complex, tr1:shared_ptr都共同准守

条款11 在operator=中处理“自我赋值”

一般而言,如果某段代码操作了pointers或references,而它们被用来,就需要处理自我赋值,如果你会运用对象来管理资源,大可不必额外操心,但如果你尝试自行管理资源,可能会掉进“在停止使用资源前意外释放它”的陷阱。如下
版本1

class Bitmap {...};
class Widget
{
    ... 
    private:
        Bitmap *pb;
};

Widget& operator = (const Widget& rhs)
{
    delete pb; 
    pb = new Bitmap(*rhs.pb);

    return *this;
}

这里自我赋值出现的问题,this已被删除,却返回一个指针指向一个已被删除的对象。版本1不具备“自我赋值安全性”和“异常安全性”
传统做法如下,版本2

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

    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

版本2存在异常方面的麻烦,如果new Bitmap导致异常(分配内存不足或Bitmap的copy构造函数抛出异常),Widget最终会持有一个指针指向一块被删除的Bitmap,这样的指针时有害的,无法安全删除,也无法读取,只能debug去找。哎

但,让operator=具“备异常安全”往往自动获得“自我赋值安全”的回报,因此愈来愈多人对“自我赋值”的处理态度是不去管它,把焦点放在“异常安全性”
版本3

Widget& operator = (const Widget& rhs)
{
    Bitmap *pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;

    return *this;
}

如果”new Bitmap”抛出异常,pb保持原状;此代码也能够处理自我赋值
版本4 copy and swap技术,一个足够好而常见的版本但是清晰性不好

class Widget
{
    ... 
    void swap(Widget& rhs); 
}

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

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

条款12 赋值对象时勿忘其每一个成分

如果你为class添加一个成员变量,你必须同时修改copying函数(拷贝构造函数和copy assign构造函数)。
条款12的意思是,当你编写一个copying函数时,请确保(1)复制所有local成员变量,(2)调用所有base classes内合适的copying函数
而且注意drived class的拷贝构造函数调用base的拷贝构造函数,不要试图调用base的cooy assign函数来初始化,对于drived的copy assign函数也一样,如果你觉得代码可能会有重复的地方,那么可以新建一个private的init()函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值