More Effective C++

  1. 条款8:各种意义下的new与delete
  2. 条款26:限制某个类所能产生的对象数量
    1. 允许0个或1个对象产生
    2. 不同的对象构造状态

条款8:各种意义下的new与delete

1.new operator 与 operator new的区别是new operator 是在外面使用的new,是语言内建的无法改变其意义:申请一块空间并在改空间上构建对象。而这两件事它都通过调用两个函数完成,第一件申请空间就是调用operator new,这个操作符可以被重载,且第一个参数永远是size_t,它负责申请空间并返回指向这块空间的地址,当然可以重载也就意味着你可以自己设计空间申请的方式

2.如果存在一块可用空间,想直接在上面构建对象则可以将operator new重载为void *operator new(size_t,void *)的形式,这个版本的operator new就叫placement new,对应的new操作也变成了new(pointerTobuffer)Type

class Widget{
    public:
    Widget(){}
    void *operator new(size_t size){
        return malloc(size);
    }
    void *operator new(size_t size,void *buffer){
        return buffer;
    }
};
int main(){
    Widget *p = new Widget;
    /*伪码
    void*memeroy = Widget::operator new(sizeof(Widget));
    Widget::Widget();
    Widget*p = static_cast<Widget*>(memory);
    */
    //其中构造函数那里是在申请的地方构造
}

delete的行为则是调用析构函数然后调用operator delete,而如果是通过placement new创建的对象,要析构时需要手动调用析构函数,因为那块内存不是由operator new申请得到的,无权去释放,应该交给申请那块地方的人处理

void *mallocShared(size_t size);
void freeShared(void* memory);

Wiget* constructWidgetInBuffer(void *buffer){
    return new(buffer) Widget;
}
void *p = mallocShared(sizeof(Widget));
Widget*p2 = constructInBuffer(p);
delete p;//error!
p2->~Widget();//OK
freeShared(p);//OK

条款26:限制某个类所能产生的对象数量

允许0个或1个对象产生

由于一个对象在产生时构造函数一定会被调用,想要阻止某个class产生对象将其constructors声明为private是最简单的

class NunStricted{
    private:
    NunStricted();
    NunStricted(const NunStricted&);
};
//如果想只存在一个对象可这样声明和设计

class PrintJob;
class Printer{
    public:
    void submit(const PrintJob&);
    void reset();
    ...
    friend Printer& thePrinter();
    private:
    Printer();
    Printer(const Printer&);
};

Printer& thePrinter(){
    static Printer p;
    return p;
}
//系统需要的操作都在函数的返回值上进行
thePrinter().reset();
thePrinter().submit(...);

当然这里这个thePrinter函数可以放进Printer类中,成一个静态函数这样可以让它们离得更近也省去了friend声明的必要

class Printer{
    public:
    static Printer& thePrinter();
};
Printer& thePrinter(){
    static Printer p;
    return p;
}

//调用就会类似下面这样
Printer::thePrinter().reset();

//也可以将thePrinter放入一个PrintStuff的namespace当中
namespace PrintStuff{
    class Printer{
        ...
        friend Printer& thePrinter();
    }
    Printer& thePrinter();
};
//上面两种方法都是为了将thePrinter函数从全局域放到特定的域当中

在这个版本的实现当中有两个点需要注意一下:

  1. 形成的唯一一个Printer对象的,是函数中的static对象而不是class的static对象。class拥有一个static对象的意思是即使它从未被用到也会被构造析构,而在函数中拥有一个static对象只有在第一次调用时才会构造,也就是说如果函数没有被调用那这个对象也不会被构造,因此这里使用的是静态函数而非静态变量的原因,另外还有一个原因就是使用静态变量无法控制其初始化顺序
  2. 这个static函数只有两行,很容易想到把它写成inline函数,早期或者说某些编译器这样会产生一些问题,即使现在最好也不要将一个内含static member的函数写成inline

另外一种设计思路就是在类里面设置一个数来记录已经生成的对象数量,当外界申请数量超过某个上限时就在constructors里抛出一个exception

class Printer{
    public:
    class TooManyObjects{};
    Printer();
    ~Printer();
    private:
    static size_t numObjects;
    Printer(const Printer&); //不允许复制行为
};
size_t Priner::numObjects = 0;
Printer::Printer(){
    if (numObjects>=1)
    throw TooManyObject;
    ...
    ++numObject;
}

Printer::~Printer(){
    ...
    --numObjects;
}

这样可以通过设定numObjects来限制特定数量的对象产生,但仍有一些问题

不同的对象构造状态

假设上面的打印机类写好了,现在要写一个升级版的打印机,它直接继承上面的打印机类

class ColorPrinter:public Printer{

};
//现在安装一台普通打印机和彩色打印机
Printer p;
ColorPrinter cp;

上述代码会因为cp在调用基类的constructor时抛出一个TooManyObjects的exception,但这并不是我们想要的
究其原因是Printer这个成分出现在了ColorPrinter内部,而上面的Printer严格限制只有一个Printer成分存在,一个Printer成分有三种可能的方式存在:

  1. 作为它本身的对象
  2. 作为base class
  3. 作为对象成员

通常是要限制对象个数而非成分的个数,如果采用第一种策略能保证正确,因为私有constructors会禁止掉派生行为.但


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 2128099421@qq.com

×

喜欢就点赞,疼爱就打赏