More Effective C++读书笔记:条款27

Aki 发布于 2022-10-21 270 次阅读


27要求(或禁止)对象产生于heap之中:

有时候,我们要求该类型的对象被分配与heap内,能够“delete this”;另一些时候,我们要求拥有某种确定性,保证某一些类型绝不会发生内存泄漏,原因是没有任何一个该类型的对象从heap中分配出来。

1、要求对象只能产生于heap之中,该怎么办?

  栈上的对象肯定调用构造方法和析构方法(离开作用域的时候),因此,要求对象只能产生于heap之中,也就是禁止栈上产生对象,解决办法有两种:将所有的构造方法声明为private,或者将析构方法声明为private。

1) 将所有的构造方法声明为private,这样就不能在栈上构造对象了。这有两点需要注意:

 a、这种情况下,不能在外部使用new operator在堆上构造对象,因为new operator要在分配的内存上,调用构造方法构造对象。因此,需要重新暴露接口,返回堆上的对象。办法有:在类内部使用new operator,暴露static方法;使用友元方法或者友元类。

  b、一个类往往有多个构造方法,必须将所有的构造方法都声明为private。对于default 构造方法,如果没有声明任何构造方法,编译器会自动生成一个。对于copy构造,没有声明,编译器也会自动生成一个。

2) 将析构方法声明为private,由于栈对象离开作用域,会自动调用析构方法,出现错误,在编译时就报错。这有两点需要注意:

 a、这种情况下,不能在外部使用delete operator删除指针,因为delete operator要调用析构方法,然后调用operator delete释放内存。因此,需要重新暴露接口,Destroy方法,在Destroy方法内部调用delete operator,由于Destroy是类内的方法,因此可以调用析构函数。

  b、相比于构造方法,析构方法只有一个,只需要将这一个析构方法声明为private就好了。

2、需要注意,将构造方法或者析构方法声明为private,将导致两个问题,那就是继承和内含,这两种情况都要调用构造方法和析构方法。

  对于继承,可以将父类的构造方法和析构方法放大访问权限,为protected。

  对于内含一个对象,修改为内含一个指针,指向对象。使用其他暴露的借口,获取堆上的对象和释放内存(Destroy)。

具体使用:

要求对象产生于heap中,意思是需要阻止clients不得使用new以外的方法产生对象。比较好的方法就是将destructor定义为private,因为constructor的类型太多,所以仍然将constructor定义为public。然后定义一个pseudo destructor来调用真正的destructor。示例如下:

class HeapBasedObject
{
public:
    HeapBasedObject() {}
    void destroy() const { delete this; }    // pseudo destructor

private:
    ~HeapBasedObject()noexcept {}
};

int main()
{
    //HeapBasedObject h;  在栈上创建对象,错误
    HeapBasedObject *ph = new HeapBasedObject;
    //delete ph;          企图访问private析构函数,错误
    ph->destroy();
}

3、禁止对象产生于heap之中,该怎么办?

禁止对象产生于heap中,则是要让clients不能使用new方法来产生对象。方法就是将operator new和operator delete定义为private。示例如下:


class NoHeapBasedObject 
{
public:
    NoHeapBasedObject() {}
    ~NoHeapBasedObject() {}

private:
    static void *operator new(size_t size) {}   //重载并定义为private
    static void operator delete(void *ptr) {}   //重载并定义为private
};

int main()
{
    NoHeapBasedObject nh;
    static NoHeapBasedObject snh;
    //NoHeapBasedObject *pnh = new NoHeapBasedObject();  错误
}