Effective C++(2)(5~12)

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

如果定义一个类

class Empty{};

相当于定义了

class Empty{
    public:
    Empty(){};  //default构造
    Empty(const Empty& rhs){};  //拷贝构造
    ~Empty();  //编译器合成的析构函数

  Empty& operator=(const Empty& rhs){};//拷贝赋值运算符 };

由于这本书标准不是C++11,有关默认移动构造和移动赋值函数等我了解了之后再补充

如果定义了带有参数的构造函数,则不再合成默认构造函数,但可以显式要求编译器合成

Empty() = default;

当类内含有引用成员或cont的成员时,编译器拒绝生成默认拷贝赋值运算符。

如果某个基类的拷贝赋值运算符为private,则编译器拒绝为其子类生成默认拷贝运算符

注意:引用只能定义一次,定义后不能改变它的指向


 

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

书上给出的方法是定义一个基类,该基类的拷贝构造和拷贝赋值为private,另你的类继承于该基类,则编译器无法合成默认拷贝构造和拷贝赋值函数。

但是在C++11中,只需要这么在函数后加上delete

class Empty{
    public:
    Empty(){};
    Empty(const Empty&) = delete;
    Empty& operator=(const Empty&) = delete;
    ~Empty();
};

07:为多态基类声明virtual析构函数

多态基类是为了通过基类指针来控制派生类对象

  • polymorphic(多态)基类应声明一个virtual析构函数, 任何一个类带有任何virtual函数,它就应该用于一个virtual析构函数

    带有虚构函数的类一般作为基类使用,而如果该类没有虚析构函数,当其派生类对象执行析构时,会导致局部销毁,进而导致内存泄漏

class Base
{
public:
    Base() = default;
    int testBase;
};

class Derived : public Base
{
public:
    int testDerived;
};
int main()
{
    Base* A = new Base;
    Derived* B = new Derived;
    A = B;  
    delete A;       //执行此语句时只会删掉Base部分的成员,而Derived的部分不会被删除
}

    注意即使基类指针指向了派生类对象,该指针也只能操作基类成员,因此要定义虚函数来操作派生类对象。

  • 类的设计如不不是为了作为基类使用,或不是为了多态性,则不应声明virtual析构函数

    因为会使对象占用内存增加。同时,为了让你的非基类、非多态用途的类不被继承,可以使用final关键字来禁止其派生。

class NoDerived final
{
     ...  
}

08:别让异常逃离虚构函数

  • 析构函数觉得不能抛出异常,如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或接受程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应提供一个普通函数(而非在析构函数中)执行该操作

对于异常处理不熟悉,先记录,以后有机会补充


 

09:绝不在构造和析构过程中调用virtual函数 

因为这类调用不会下降至派生类

考虑:

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

Transaction::Transaction()
{
    logTransaction();
}

class BuyTransaction : public Transaction{
public:
    virtual void logTransaction() const;
};

BuyTransaction a;

在定义a时,会先构造基类Transaction,此时调用的将会是基类的logTransaction(),因为在派生类对象的基类构造期间对象类型为基类而非派生类。

因此构造函数和析构函数都不能调用virtual函数

一种解决方案:

class Transaction
{
public:
    explicit Transaction(const std::string &logInfo);
    void logTransaction(const std::string &logInfo) const; //非虚函数
};

Transaction::Transaction(const std::string &logInfo)
{
    //...
    logTransaction(logInfo);
}

class BuyTransaction : public Transaction
{
public:
    BuyTransaction(parameters) : Transaction(createLogString(parameters)) {}

private:
    static std::string createLogString(parameters);
};
//令派生类将必要构造信息向上传给基类狗在函数
//从而让构造函数拜托虚函数

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

为了实现连锁赋值,即a = b = c = 15这种赋值,则需要这么做

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

此协议使用于所有赋值运算,+= , -= , ...


 

11:在operator=中处理"自我赋值"

 

 考虑如下代码

class widget
{
public:
    widget &operator=(const widget &rhs);

private:
    widget *wp;
};

widget &widget::operator=(const widget &rhs)
{
    delete wp;
    wp = new widget(*rhs.wp);
    return *this;
}

当自我赋值时,会造成未定义行为,因为rhs在被删掉后还在使用,有三种方法解决

使用“证同测试”来避免这个问题

widget &widget::operator=(const widget &rhs)
{
    if (this == &rhs)
    {
        return *this;
    }
    
    delete wp;
    wp = new widget(*rhs.wp);
    return *this;
}

精心周到的语句排序

widget &widget::operator=(const widget &rhs)
{
    widget* old = wp;
    wp = new widget(*rhs.wp);
    delete old;
    return *this;
}

copy and swap技术

class widget
{
public:
    widget &operator=(const widget &rhs);
    void swap(widget& rhs); //交换*this和rhs的数据

private:
    widget *wp;
};

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

或使用值传递的方式,这里会产生副本,并让*this与副本的数据互换

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

 

12:复制对象时勿忘其每一个成分

考虑如下代码

class Base
{
private:
    int base;
};

class Derived : public Base
{
public:
    Derived(const Derived &rhs);
    Derived &operator=(const Derived &rhs);

private:
    int derived;
};

Derived::Derived(const Derived &rhs) : derived(rhs.derived)
{
    //do something
}

Derived& Derived::operator=(const Derived& rhs)
{
    //do something
    derived = rhs.derived;
    return *this;
}

它们并未对Base部分进行初始化,会调用默认构造函数,而这往往造成不想要的结果。

考虑到Base部分往往是private的,可以使用相应base部分的拷贝构造函数

Derived::Derived(const Derived &rhs) : Base(rhs), derived(rhs.derived)
{
    //do something
}

Derived& Derived::operator=(const Derived& rhs)
{
    //do something
    Base::operator=(rhs);
    derived = rhs.derived;
    return *this;
}

 另外,不要让拷贝构造函数和拷贝赋值函数相互调用,如果其中重复代码较多,应放入第三个函数,由两者共同调用

posted @ 2022-03-05 20:43  帝皇の惊  阅读(37)  评论(0)    收藏  举报