技术
条款25:将constructor和non-member function虚化
将函数虚化带来的好处是可以适应变化、可以通过基类指针调用不同子类的重载函数【大部分都会应用的技术】
条款26:限制某个class所能产生的对象的数量
-
0~1个对象:
方法:
1.将构造函数声明为private,或者使用单例
class Printer
{
private:
Printer();
~Printer();
public:
//比较简洁的一种单例方式,没有使用指针
friend Printer& thePrinter();
};
Printer& thePrinter()
{
static Printer p;
return p;
}
//或者可以将此函数放在类中,将函数声明为static
static Printer& Printer::thePrinter()
{
static Printer p;
return p;
}
类拥有一个static对象,意味着即使从未用到,也会被构造;
函数拥有一个static对象,意味着对象在函数第一次调用时才产生
2.使用类似计数器,记录已经构造的数量,声明形式:static int num;
一条重要的设计原则:避免具体类继承其它具体类
#pragma once
#include <iostream>
#include <memory>
using namespace std;
//模板化一个用于计数的类,能够完成不同类的统计过程
template<class T>
class Counted
{
public:
static int objCount() { return num; }
protected:
Counted()
{
init();
}
Counted(const Counted& rhs)
{
init();
}
~Counted()
{
--num;
cout << "~Counted() count:" << num << endl;
};
private:
static int num;
static const size_t max;
void init()
{
if (num >= max)
{
cout << "over max";
throw;
}
++num;
cout << "Counted::init() num = " << num << endl;
}
};
class Printer:private Counted<Printer>
{
public:
static std::unique_ptr<Printer> makePrinter()
{
std::unique_ptr<Printer> pInt(new Printer());
return pInt;
}
~Printer();
using Counted<Printer>::objCount; //必须这样声明,因为是私有继承
private:
Printer();
private:
};
Printer::Printer()
{
}
Printer::~Printer()
{
}
条款27:禁止对象产生于堆栈中
保证对象不产生于堆栈中从侧面也保证了不会发生内存泄漏。
1.首先如何让对象只在堆栈中产生?
方法:non-heap 对象会在定义点自动构造,在结束时自动析构,因此只要让隐式调用的构造动作和析构动作不合法,就可以办到只从堆栈产生。将destructor 声明为protected(用于支持继承),定义一个伪析构函数:
class A
{
public:
A();
void destruct() const {delete this;} //伪析构函数
...
private:
~A();
};
A a; //error
A* b = new A() //success
b->destruct(); //success
2.如何判断对象是否位于heap内?
答案:无法判断
- 使用 operator new? 【完成不了】
当对象被分配于heap,operator new会被调用,用于分配原始内存。而后会调用构造函数,将此次的内存用于初始化对象,因此可以在operator new函数中设置标识,表示此次分配为堆栈动态分配内存
class B
{
public:
//在分配内存时将标识设置为ture
static void* operator new(size_t size)
{
onHeap = true;
}
//在构造函数中判别是否是在堆栈中分配的
B()
{
if(onHeap)
throw;
}
private:
bool onHeap = false;
};
完成不了的原因是 :A* array = new A[10];
数组内存分配的时候调用的时operator new[]而不是 operator new,当然也可以对operator new[]做同样的手法,但是比较麻烦的是假设A有10个元素,因此会有10次constructor调用,但整个程序只分配了一次内存,因此只有第一次动作为true,其余都为false。
3.为什么要判断对象在heap内?
实际上做这样目的通常是为了判断调用delete是否安全,那么应该换种思路,如何确定指针的删除动作是否安全?
使用:抽象混合式基类(abstract mixin base class)
- abstract base class: 不能被实例化的基类
- mixin(mix in): class提供一组定义完好的 能力,能够与其子类提供的其它任何能力兼容
如下:提供一个抽象混合式基类,用来为子类提供判断某指针是否以 operator new 分配出来的
class HeapTracked{
public:
virtual ~HeapTracked = 0;
//分配内存时将指针存储到容器中,确定当前的内存分配是在堆栈中
static void* operator new(size_t size)
{
void* mePtr = ::operator new(size);
addresses.push_front(mePtr);
return mePtr;
}
static void operator delete(void* ptr)
{
auto it = find(addresses.begin(),addresses.end(),ptr);
if(it != addresses.end())
{
::operator delete(ptr);
}
else
{
throw //抛出异常,表示非堆栈对象
}
}
bool isOnHeap() const
{
const void* rawAdd = dynamic_cast<const void* >(this); //将this转为const void*会返回一个指向现行对象的内存起始处,也就是operator new分配的位置
auto it = find(addresses.begin(),addresses.end(),rawAdd);
return it != addresses.end()
}
private:
typedef const void* RawAddress;
static list<RawAddress> addresses;
};
要想对某个类对象做上述判断,只需要继承HeapTracked类就可以
class A:public HeapTracked{
public:
void inventory(const A* ap)
{
if(ap->isOnHeap())
{
...
}
else
{
...
}
}
};
4.禁止对象产生于heap中?
一般有三种情况需要阻止:
- 对象被直接实例化
- 在继承体系中的子类对象实例化基类的部分
- 对象被内嵌在其它对象中
- 阻止对象直接实例化
想阻止对象直接实例化于heap中,只需要让用户无法调用new就可以,由于new operator总是会调用operator new函数,因此,可以将operator new声明为private,就可以完成这个操作
class A{
public:
private:
static void* operator new(size_t size);
static void operator delete(void* ptr);
};
A a; //success
A* b = new A(); //error 调用了私有的operator new函数
- 继承下实例化基类部分
如果按照上述的做法,将operator new声明为private,且子类并没有在其public下声明这两个函数,那么子类就继承了父类那两个函数的private版本,使用动态内存分配子类对象时同样会报错。
- 对象被内嵌在其它对象中
对象内嵌在类中在其operator new函数声明为private的情况下同样有效。
总结:要禁止对象在heap中产生,则只需要将operator new/delete 或者operator new[]/delete[]声明为private就可以,这种方法在上述三种情况下都能运行正常