您的位置:首页 > 编程语言 > C语言/C++

我的C++实践(14):限制类对象的个数

2009-09-15 18:37 267 查看
    1、允许一个或零个对象:
把所有的构造函数声明为私有,则不能创建任何对象。如果允许只创建一个对象,可用一个全局函数(或静态成员函数)来创建唯一的一个静态对象,并返回其引用,为提高效率,可把全局函数声明为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函数变成公有,这样就可以用它来获取当前对象的个数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息