欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Effective c++ 读书总结 6 - 10

程序员文章站 2022-03-06 11:58:32
...

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


当一个类不需要自动生成的函数( 构造、拷贝构造、拷贝运算符、析构)时、有以下两种方式解决:

1.主动将函数声明为private

2.继承一个有限制的base类:derived类自动生成的函数会企图调用base中对应的函数、这时就会编译出错。

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

1.没声明virtual析构函数导致的内存泄漏

当Base* b = new Derived();父类指针指向一个子类实例时、如果base没有将析构函数声明为virtual、delete b;释放时只会调用父类的析构函数、导致对象的子类部分没有被删除、而内存泄漏。

2.如果有一个virtual成员函数、那么就应该声明virtual析构函数

如果一个类有virtual函数、那么也就是说这个类希望成为一个父类、那么为了避免内存泄漏、需要声明virtual析构函数、另外如果一个类没有virtual成员、就意味着它不希望成为父类、所以也不要声明virtual析构函数、因为那会浪费空间(一个指向virtual table的指针)、在我们自定义类时如果不希望它成为父类、使用关键字final(之前没有、新版本有了)

3.virtual table

对于子类继承父类并且重写virtual函数这样情况、对象就必须携带某些信息(因为要在运行期确定具体调用的是哪个函数、父类的还是子类的)、有一个指向virtual table的一个指针、virtual table中都是指向调用函数的函数指针、32位计算机每个指针4个字节64位就是8个字节。指针vptr。

4.希望有一个抽象类、但是没有pure virtual函数、可将析构声明为pure virtual

virtual ~Test() = 0;抽象类不能实例化。因为在释放子类对象时调用完子类的析构函数还要调用父类的析构、那么这个pure virtual析构需要一份实现、不然则会发生链接期错误。

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


1、析构函数抛出异常可能会造成内存泄漏

析构函数中要做的工作通常是释放内存资源、但是如果在释放资源时抛出异常、那么后面的对象就不能释放、造成泄漏、像:

class Test
{
private:
    vector<NODE*> nodes;
public:
    ~Test()
    {
        for(auto node : nodes)
        {
            delete node;    //如果在删除时抛出异常、那么数组中后面的元素都不会被释放了
            node = NULL;
        }
    }
};

2、析构函数抛出异常的两种处理方式

第一种方式就是结束程序:如果程序遭遇一个析构期间发生的错误、且后续无法继续执行、那么结束程序是一个合理的选项。

Test::~Test()
{
    try{
        close();    //捕捉close函数抛出的异常
    }catch(Exception e)
    {
        abort();    //直接结束程序
    }
}

第二种方式是析构函数吞下异常( 当不影响后续程序执行时可以采用 )

3、提供一个释放资源的接口

很多时候为了防止忘记释放资源、释放资源的操作都在析构函数中。但是一旦析构函数抛出异常就只能使用上述说明的两种方式来处理、这时调用者完全不知情、并且他没有权利对异常进行自定义的处理、所以我们可以将释放资源的操作写在一个release函数中、然后让析构函数也调用这个函数、这样用户可以自定义处理异常、也可以忽略( 使用析构释放 )、这样做是给用户提供了处理异常的机会。

class Test
{
private:
    vector<NODE*>     nodes;
    bool              releaseflag;
public:
    ~Test()
    {
        try{
            release();
        }catch(Exception e){
            abort();
        }
    }
    void    release()
    {
        if(releaseflag == false){
            for(auto node : nodes){
                delete node;
                node = NULL;
            }
            releaseflag = true;
        }
    }  
};

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


1、子类对象没有构造完成就调用virtual函数、可能产生不想要的结果

class Test
{
public:
    Test(){
        print();    
    }
public:
    virtual void print();
};

class TT : public Test
{};

int main()
{
    TT t;    //在父类构造期间、调用virtual函数。此时子类部分还没有构造、当前的类型型别是Test、
             //所以调用的是父类的print函数、如果父类没有实现、则链接期错误、如果父类实现了、
            //就调用父类的、但是类的设计其实是想根据不同的子类实例执行不同的print函数、这里做不到
    return 0;
}

正确的方式可以

class Test
{
private:
    void print(int n){
        cout<<n<<endl;
    }
public:
    Test(int n){
        print(n);
    }
};

class A : public Test
{
public:
    A():Test(0){
        //使用参数将子类信息传递到父类、执行统一的操作
    }
};

3、要注意构造和析构调用的成员函数中可能包含virtual函数(避免)

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


1、为了实现连锁赋值、赋值操作符必须返回一个左值(reference to *this)

class String
{
private:
    char buf[SIZE];
public:
    String& operator=(const String& str)
    {
        strcpy(buf,str.buf);
        return *this;
    }
}

 

相关标签: 读书笔记