我的C++实践(14):限制类对象的个数
2009-09-15 18:37
267 查看
1、允许一个或零个对象:
把所有的构造函数声明为私有,则不能创建任何对象。如果允许只创建一个对象,可用一个全局函数(或静态成员函数)来创建唯一的一个静态对象,并返回其引用,为提高效率,可把全局函数声明为inline。注意这个全局函数要声明为类的友元函数,因为要使用私有的构造函数。
例如我们想创建一个打印机对象,但希望实际上只有一个打印机,可以这样写:
这里我们把thePrinter()实现为一个全局函数,里面只创建一个静态对象,返回其引用,这样客户端代码中的所有thePrinter()调用都只使用这个唯一的静态对象。注意,对构造函数,可以只声明为私有,如果不使用它,则可以不提供定义,但对需要被使用的构造函数(因为是私有,只能在类的成员函数或友元函数中使用)则必须提供定义。当然,我们也可以把thePrinter()实现为Printer类的静态成员函数,这样就不需要友元声明了。但使用全局函数也有优势,因为在某些类中声明的静态对象即使没用到也会被构造(以及析构)出来,而全局函数里的静态对象只有执行函数时才被创建。
另一方面,我们有时候会把那个唯一的p对象声明为全局的静态对象或Printer类的静态成员,然后在thePrinter()或Printer::thePrinter()中直接返回它,这不是一种好的实现方案。我们称函数内的static对象称为local static对象,其他的static对象(class作用域、namespace作用域、global作用域、file作用域)称为non-local static对象。C++只保证在一个特定编译单元内的静态对象的初始化顺序,对不同编译单元内的non-local static对象的初始化次序没有明确定义。可见这样的实现方案会使得p对象的初始顺序不明确。
当我们需要用到non-local static对象,又要保证正确的初始化顺序时,解决方法恰恰就是将non-local static对象放在一个inline包装函数中,函数返回一个引用指向此对象(变成local static对象)。用函数调用来替换直接访问non-local static对象,这时函数内的局部静态对象就会有确定的初始化顺序(在第一次执行函数时初始化)。注意,我们通常在设计时并不建议返回指向函数内局部对象的reference,但这里的包装函数行为单纯,只是包装一个static对象并返回其reference,并不做其他工作。当多次调用包装函数时,reference指向同一个static对象,并且值均相同(包装函数内并没有改变对象的成员值),因此这样实现没有副作用。
注意,构造函数声明为私有的类不能作为基类使用,也不能组合到其他类中来使用。我们可以放宽一点,把构造函数声明为protected的,这样类同样不可以创建对象,但可以被继承。
2、允许创建任意数量的对象,但不允许作为基类:
把构造函数声明为私有,这样就不允许作为基类了。同时我们提供相应的伪构造函数,用它来创建对象并返回。例如对有限状态机类,如下:
客户端使用这个类时,就必须调用伪构造函数来创建对象。当然,由于对象是动态分配的,这时客户必须自己调用delete来删除对象。
3、允许对象个数限制在某个给定的值:
使用前面在“模板与继承相结合的威力”中介绍的奇异递归模板模式技术,编写一个具有实例计数功能的基类即可,让需要计数的类继承这个类。因为类要实现计数功能,是一种“实现”的关系,故最好用私有继承,表示用这个基类来实现需要的功能。这里我们做一下修改,当需要计数的类对象个数超过上限时就抛出异常。注意不同的类可以通过特化这个基类的表示上限值的成员,以满足不同类对象个数的要求。
这里ObjectCounter<T>模板用于计数,它需要作为基类,成员名的前面最好加类作用域符(也可用this->),使它变成依赖型名称。表示对象个数上限值的maxObjects成员是一个静态常量,它并没有初始化。我们必须在需要计数的类MyString、Printer中指定它的值。不同类的对象个数上限可能不同,因此我们只要特化maxObjects这个成员并指定需要的值即可(类模板可以只特化某个成员)。在MyString、Printer这些类中,由于是私有继承,因此要用using声明把objectCount函数变成公有,这样就可以用它来获取当前对象的个数。
把所有的构造函数声明为私有,则不能创建任何对象。如果允许只创建一个对象,可用一个全局函数(或静态成员函数)来创建唯一的一个静态对象,并返回其引用,为提高效率,可把全局函数声明为inline。注意这个全局函数要声明为类的友元函数,因为要使用私有的构造函数。
例如我们想创建一个打印机对象,但希望实际上只有一个打印机,可以这样写:
//printer.hpp:只允许创建一个打印机对象,即单例模式 #ifndef PRINTER_HPP #define PRINTER_HPP class PrintJob{ public: PrintJob(const string& whatToPrint){ //... } //... }; class Printer{ //打印机类 private: //所有构造函数都声明为私有,则不能作为基类使用 Printer(){ //... } Printer(Printer const& rhs){ //... } friend Printer& thePrinter(); //... public: void submitJob(PrintJob const& job){ //... } void reset(){ //... } void performSelfTest(){ //... } //... }; inline Printer& thePrinter(){ //获取唯一的一个打印机对象 static Printer p; return p; } //... #endif
这里我们把thePrinter()实现为一个全局函数,里面只创建一个静态对象,返回其引用,这样客户端代码中的所有thePrinter()调用都只使用这个唯一的静态对象。注意,对构造函数,可以只声明为私有,如果不使用它,则可以不提供定义,但对需要被使用的构造函数(因为是私有,只能在类的成员函数或友元函数中使用)则必须提供定义。当然,我们也可以把thePrinter()实现为Printer类的静态成员函数,这样就不需要友元声明了。但使用全局函数也有优势,因为在某些类中声明的静态对象即使没用到也会被构造(以及析构)出来,而全局函数里的静态对象只有执行函数时才被创建。
另一方面,我们有时候会把那个唯一的p对象声明为全局的静态对象或Printer类的静态成员,然后在thePrinter()或Printer::thePrinter()中直接返回它,这不是一种好的实现方案。我们称函数内的static对象称为local static对象,其他的static对象(class作用域、namespace作用域、global作用域、file作用域)称为non-local static对象。C++只保证在一个特定编译单元内的静态对象的初始化顺序,对不同编译单元内的non-local static对象的初始化次序没有明确定义。可见这样的实现方案会使得p对象的初始顺序不明确。
当我们需要用到non-local static对象,又要保证正确的初始化顺序时,解决方法恰恰就是将non-local static对象放在一个inline包装函数中,函数返回一个引用指向此对象(变成local static对象)。用函数调用来替换直接访问non-local static对象,这时函数内的局部静态对象就会有确定的初始化顺序(在第一次执行函数时初始化)。注意,我们通常在设计时并不建议返回指向函数内局部对象的reference,但这里的包装函数行为单纯,只是包装一个static对象并返回其reference,并不做其他工作。当多次调用包装函数时,reference指向同一个static对象,并且值均相同(包装函数内并没有改变对象的成员值),因此这样实现没有副作用。
注意,构造函数声明为私有的类不能作为基类使用,也不能组合到其他类中来使用。我们可以放宽一点,把构造函数声明为protected的,这样类同样不可以创建对象,但可以被继承。
2、允许创建任意数量的对象,但不允许作为基类:
把构造函数声明为私有,这样就不允许作为基类了。同时我们提供相应的伪构造函数,用它来创建对象并返回。例如对有限状态机类,如下:
//fsa.hpp:有限状态机类,允许创建任意数量的对象,但不允许作为基类 #ifndef FSA_HPP #define FSA_HPP class FSA{ //有限状态机类 private: //构造函数声明为私有 FSA(){ //... } FSA(FSA const& rhs){ //... } public: static FSA* makeFSA(){ //伪构造函数 return new FSA(); } static FSA* makeFSA(FSA const& rhs){ //伪拷贝构造函数 return new FSA(rhs); } //... }; #endif
客户端使用这个类时,就必须调用伪构造函数来创建对象。当然,由于对象是动态分配的,这时客户必须自己调用delete来删除对象。
3、允许对象个数限制在某个给定的值:
使用前面在“模板与继承相结合的威力”中介绍的奇异递归模板模式技术,编写一个具有实例计数功能的基类即可,让需要计数的类继承这个类。因为类要实现计数功能,是一种“实现”的关系,故最好用私有继承,表示用这个基类来实现需要的功能。这里我们做一下修改,当需要计数的类对象个数超过上限时就抛出异常。注意不同的类可以通过特化这个基类的表示上限值的成员,以满足不同类对象个数的要求。
//objectcounter.hpp:对对象创建进行记数的模板 #ifndef OBJECT_COUNTER_HPP #define OBJECT_COUNTER_HPP #include <cstddef> template<typename T> class ObjectCounter{ //用来记录T型对象构造的总个数 private: static std::size_t numObjects;//存在对象的个数 static const std::size_t maxObjects; //对象个数的上限,由需要计数的类来指定 void incr(){ if(ObjectCounter<T>::numObjects>=ObjectCounter<T>::maxObjects) throw TooManyObjects(); ++ObjectCounter<T>::numObjects; } protected: ObjectCounter(){ //缺省构造函数 incr(); } ObjectCounter(ObjectCounter<T> const& rhs){ //拷贝构造函数 incr(); } ~ObjectCounter(){ //析构函数 --ObjectCounter<T>::numObjects; } public: class TooManyObjects{ }; //异常类 static std::size_t objectCount(){ //返回当前对象个数 return ObjectCounter<T>::numObjects; } }; //static成员变量必须在类外进行定义,由于是非const的,因此也要类外初始化 template<typename T> std::size_t ObjectCounter<T>::numObjects=0; #endif
//mystring.hpp:使用了对象计数的MyString类 #ifndef MYSTRING_HPP #define MYSTRING_HPP #include "objectcounter.hpp" class MyString : private ObjectCounter<MyString>{ public: //由于是私有继承,要用using把objectCount函数变成公有 using ObjectCounter<MyString>::objectCount; //... }; //特化ObjectCounter的maxObjects成员,值为3,以限制MyString类对象的个数, //这个语句必须加入到MyString类的实现文件中 template<> const std::size_t ObjectCounter<MyString>::maxObjects=3; #endif
//countprinter.hpp:使用了对象计数的Printer类 #ifndef PRINTER_HPP #define PRINTER_HPP #include "objectcounter.hpp" class Printer : private ObjectCounter<Printer>{ public: //由于是私有继承,要用using把objectCount函数变成公有 using ObjectCounter<Printer>::objectCount; //... }; //对Printer,对象个数特化为值4 template<> const std::size_t ObjectCounter<Printer>::maxObjects=4; #endif
//countertest.cpp:客户端代码,对对象计数进行测试 #include <iostream> #include "mystring.hpp" #include "countprinter.hpp" int main(){ MyString s1,s2; std::cout<<"number of MyString: " <<MyString::objectCount()<<std::endl; std::cout<<"number of MyString: " <<s1.objectCount()<<std::endl; s1.~MyString(); //减少一个对象 std::cout<<"number of MyString: " <<MyString::objectCount()<<std::endl; try{ MyString s3,s4,s5; //对象个数超过上限,会抛出出异常 }catch(ObjectCounter<MyString>::TooManyObjects& e){ std::cout<<"Too many MyString objects."<<std::endl; } Printer t1,t2,t3; std::cout<<"number of Printer: " <<Printer::objectCount()<<std::endl; std::cout<<"number of Printer: " <<t1.objectCount()<<std::endl; Printer t4,t5; //对象个数超过上限,会抛出出异常,终止程序 return 0; }
这里ObjectCounter<T>模板用于计数,它需要作为基类,成员名的前面最好加类作用域符(也可用this->),使它变成依赖型名称。表示对象个数上限值的maxObjects成员是一个静态常量,它并没有初始化。我们必须在需要计数的类MyString、Printer中指定它的值。不同类的对象个数上限可能不同,因此我们只要特化maxObjects这个成员并指定需要的值即可(类模板可以只特化某个成员)。在MyString、Printer这些类中,由于是私有继承,因此要用using声明把objectCount函数变成公有,这样就可以用它来获取当前对象的个数。
相关文章推荐
- 我的C++实践(14):限制类对象的个数
- 我的C++实践(14):限制类对象的个数
- More Effective C++ 条款26 限制某个class所能产生的对象数量
- 《more effective c++》条款26 限制类对象的个数
- 【C++】限制某个类所能产生的对象数量
- C++实现一个限制对象实例个数的类
- More Effective C++ (限制类的对象数量)
- 谭浩强版C++课本实例 第三章 关于类和对象的进一步讨论(14)
- 信管14:c++:定义对象示例:学生类
- more effective C++设计模式限制对象产生
- C++中限制对象的申请
- [总结]C++实现一个限制对象实例个数的类
- ModernC++开发实践--如何高效的传递对象给异步方法
- C++如何限制类对象只能静态分配或者只能只能动态分配
- 连载:面向对象葵花宝典:思想、技巧与实践(14) - 面向对象开发技术流程
- 我的C++实践(11):存放异类对象的元组类型
- 连载:面向对象葵花宝典:思想、技巧与实践(14) - 面向对象开发技术流程
- 给大家介绍对象 - C++快速入门14
- C++ 学习笔记(14)重载运算与类型转换、函数对象、function库
- 14、不一样的C++系列--对象销毁