读到《More Effective C++》中的“计算对象个数的Base Class”一节时,感觉书中讲的例子很有特点,故在此记录下来,顺便按照个人的一点想法稍微改善一下。
先讲需求,这里要设计一个可以控制某个类型所存在对象个数的基类,使得继承这个基类的类型可以极简地控制其某一时刻实例化的对象个数。
《More Effective C++》书中对于这个基类的实现源码如下:
template<class BeingCounted>
class Counted {
public:
class TooManyObjects {}; // 此乃可能被抛出的exception。
static int objectCount() { return numObjects; }
protected:
Counted() { init() }
Counted(const Counted& rhs) { init() }
~Counted() { --numObjects }
private:
static int numObjects;
static const size_t maxObjects;
void init() {
if (numObjects >= maxObjects) throw TooManyObjects();
++numObjects;
}
};
template<class BeingCounted> int Counted<BeingCounted>::numObjects = 0;
template<class BeingCounted> size_t Counted<BeingCounted>::maxObjects = 1; // 用户可自定义最多对象数目
源码十分好理解,唯一需要注意的是最后一行的maxObjects需要用户自行在某处定义,以达到用户控制最大对象数目的目的。书中对此的解释是,如过用户没有定义maxObjects,编译器也会报“无法解析的外部命令”这种错误,以提醒用户去定义这个maxObjects。
对于这个基类说实话我是感觉挺巧妙的,封装效果挺不错,然而唯独这个maxObjects让我感觉很不优雅。然后我又想到了,这不正是非类型模板参数解决的问题吗,把这个maxObjects放到模板参数列表中,并给它一个默认值,用户自定义类继承这个基类时便可在模板列表中直接指定maxObjects了。
想到就做,以下是我对这个Counted类稍作修改后的源码:
template<class BeingCounted, int maxObjects = 1> // 默认最大对象数目为1
class Counted {
public:
static int objectCount() { return numObjects; }
class TooManyObjects : public std::exception {
public:
const char *what() const {
return "太多对象了";
}
};
protected:
Counted() { init(); }
Counted(const Counted& rhs) { init(); }
~Counted() { --numObjects; }
private:
static int numObjects;
void init() {
if (numObjects >= maxObjects) {
throw TooManyObjects();
}
++numObjects;
}
};
template<class BeingCounted, int maxObjects>
int Counted<BeingCounted, maxObjects>::numObjects = 0;
我所作的修改很少,仅仅是把maxObjects提到了模板参数列表中。此外,我修改了TooManyObjects这个异常类,让它继承自std::exception,并重写what()方法,当然,这是为了后续测试方便。
接下来,随便写一个类继承自这个Counted基类,并测试:
class handsome: public Counted<handsome, 3> { // 此处修改最大对象数目为3
public:
handsome() {
std::cout << this->objectCount() << std::endl; // 构造函数中输出当前的对象数。
}
};
int main() {
try
{
handsome h;
handsome h2;
handsome h3;
handsome h4;
}
catch (const std::exception e) // 注意这里是有问题的
{
std::cout << e.what() << std::endl;
}
}
以下为输出结果:
1
2
3
Unknown exception
验证证明,控制对象数这个逻辑是正常的,且使用十分方便。但唯一有个问题,最后报错时调用what()输出的不是我们想要的东西,这个what执行的是std::exception::what()而不是我们的重写版本。刚开始我还纠结了很久怀疑函数签名写错了导致没重写成功,后来才发现,是catch中的参数列表少了引用符号。(这个问题其实《More Effective C++》也讲到了,然而没想到这么快就遇到了。。。)
exception的参数传递同函数的参数传递不太一样,不论是catch中是以by value还是by reference(没法by pointer)传递,throw的对象都会发生复制行为,如果是以by value传递,这个复制行为甚至会发生2次,这是为了防止抛出的异常对象在脱离作用域时销毁。
而以by value方式传递时,如果catch中的参数类型是抛出对象的类型的父类,此时会发生裁切(slice),即对该对象进行裁剪,只复制父类对象部分(这里的复制是第2次复制,第1次复制还是复制的完整对象)。故理所当然的,此时catch中的参数也就丢失了多态性,这也是我前面例子出错的原因了。而只有以by reference传递,才能保留多态性。
问题找到了,那就很好调整了:
int main() {
try
{
handsome h;
handsome h2;
handsome h3;
handsome h4;
}
catch (const std::exception& e) // 改成引用传参
{
std::cout << e.what() << std::endl;
}
}
1
2
3
太多对象了
问题解决~