singleton
pattern,又称单件模式,或者单例模式。singleton要求类有且仅有一个实例,并给其他对象提供这一实例。
控制类实例仅有一个,办法有两个:
1.私有化构造函数与copy构造函数,使用一个函数静态变量
1 #include <iostream>
2 class People{
3 private:
4 People(std::string name,int age);
5 People( const People&);
6 public:
7 friend People& MadePeople( );
8 public:
9 void show( );
10 private:
11 std::string name;
12 int age;
13 };
14
15 void People::show( )
16 {
17 std::cout<<"The people's name is "<<name<<std::endl;
18 std::cout<<"The people's age is "<<age<<std::endl;
19 }
20
21 People& MadePeople( )
22 {
23 static People onlyPeople("tom",16);
24 return onlyPeople;
25 }
26
27 int main( int argc,char **argv)
28 {
29 MadePeople( ).show( );
30 return 0;
31 } |
结果为:
将对象的构造函数与copy构造函数声明为私有,那么只能通过类的成员函数或者是友元函数来调用构造函数,那么这里我们将只提供一个友元函数来调用该类的私有构造函数,那么如何确保实例化只有一个呢?干脆声明一个静态变量(静态变量这里就不详细深入了,如果有该静态变量,编译器是直接引用的,而不是重新构造一个新的静态变量赋值),而该静态变量获取通过友元函数。
注意这里是函数静态成员不是类的静态成员,类中的静态实际上总是被构造的,即使不使用该对象,但是在函数中的静态成员,如果没有调用该函数,就不会建立该对象,但是每次这个函数被调用时候都会检查是否需要建立对象)。我们应该尽量做到为不需要为我们不用东西付出。而且函数成员的静态变量初始化时间是我们能确定的(第一次调用该函数时候),但是类的成员静态变量是不能确定的。C++为一个object文件的源代码的集合内静态成员初始化提供某种保证,但是对于不同的源代码集合中静态成员初始化顺序没有这种保证,这些会给我们带来麻烦。例如一个需要初始化的静态变量是根据另外一个静态变量来的,但是有可能这个静态变量初始化顺序还在需要初始化的静态变量之后。
这里提醒一下如果看过C++你最好不要做的,你就可能会要说了,不是建议不要写成引用返回值吗?因为现在的情况已经很明确了,只会调用一次来创建People对象的实例。并会赋值到一个People对象。如果在多线程的环境下,该资源就是一个互斥资源,就需要注意使用“锁”来应对相应的问题。所以是可以在函数中建立静态变量。那么通过使用引用从而避免了临时对象的创建的性能消耗。(如果在GCC编译器中,编译器会在返回值创建临时变量这块会做相应的优化)
2.私有化copy构造函数,使用类的静态变量。
1 #include <stdexcept>
2 #include <iostream>
3 class TooManyPeople:public std::exception
4 {
5 public:
6 TooManyPeople(std::string e)
7 :errorMsg(e)
8 {}
9 ~TooManyPeople( ) throw( ) {}
10 const char * what( ) const throw( );
11 private:
12 std::string errorMsg;
13 };
14 const char* TooManyPeople::what( ) const throw( )
15 {
16 return errorMsg.c_str( );
17 }
18 class People{
19 private:
20 static std::size_t numPeople;
21 People(const People&);
22 public:
23 People(std::string name,int age);
24 ~People( );
25 private:
26 std::string name;
27 int age;
28 };
29 size_t People::numPeople = 0;
30 People::People(std::string n,int a:w )
31 {
32 if(numPeople > = 1)
33 {
34 throw TooManyPeople("Error:Too Many People");
35 }
36 name=n;
37 age=a;
38 ++numPeople;
39 }
40
41 People::~People( )
42 {
43 --numPeople;
44 }
45
46 int main(int argc,char **argv)
47 {
48 try
49 {
50 People p1("tom",17);
51 People p2("sam",15);
52 }
53 catch(std::exception &e)
54 {
55 std::cerr<<e.what( )<<std::endl;
56 }
57 return 0;
58 } |
运行结果为
这里开放了一个方法来实例化该对象,我们在该方法中加入了逻辑判断,并在类中有一个静态变量来指示该类的实例化数量是多少?当该指示变量大于等于1的时候,我们就封锁该方法,禁止实例化更多的对象,小于的时候就进行实例化,并修改指示变量。所以这里完成了限制实例化个数为1的要求。(这里提示下,如果在多线程下面,指示变量是一个互斥资源)
扩展
现在假设我们规定,环境里只有一个男人和一个女人
class Man:public People{ ... };
class Women:Public People{ ... };
Man m;
Women w; |
这里按我们本意是行的通的,控制每一个类只有一个对象,但是实际上,这里建立Women对象是行不通的。因为构造Man会调用基类构造函数People(
),在构造Women的时候调用构造函数People( ),就会报错。当然当其他类包含People对象时候,会发生同样的问题。
1 class Robot
2 {
3 private:
4 ...
5 People p; //有类似人的能力
6 ...
7 }
8 Robot h1;
9 Robot h2; //错误 |
这里被嵌入更大的对象,作为它派生类的基类,这些混淆了存在对象的数目,编程的本意与编译器不一致。通常我们仅仅对对象本身存在的情况做限制,而不是其他对象。如果采用第一种方式实现单例模式方法,就很容进行这种限制,而且不会影响到其他对象。因为People构造函数是private,带有private构造函数的类不能作为基类使用,也不能嵌入到其他对象中。那么下面一种给我们提供了解决上面因被继承或者嵌入其他对象中造成了混淆的问题思路,如下:
1 class T{
2 public:
3 static T * makeT( );
4 static T * makeT( const T& rhs);
5 ...
6 private:
7 T( );
8 T(const T& rhs);
9 ...
10 };
11 T * T::makeT( )
12 {
13 return new T( );
14 }
15
16 T* T::makeT( const T& rhs)
17 {
18 return new T(rhs);
19 } |
那么摆脱上述两种现象问题,在此前代码基础上改正可以得到以下代码:
1 #include <stdexctpt>
2 #include <iostream>
3 #include <memory>
4 class tooManyPeople:public std::exception
5 {
6 public:
7 TooManyPeople(std::string e)
8 :errorMsg(e)
9 {}
10 ~TooManyPeople( ) throw( ) {}
11 const char * what( ) const throw( );
12 private:
13 std::string errorMsg;
14 };
15 const char* TooManyPeople::what( ) const throw( )
16 {
17 return errorMsg.c_str( );
18 }
19 class People{
20 private:
21 static std::size_t numPeople;
22 People(const People&);
23 People(std::string name,int age);
24 public:
25 static People* MakePeople(std::stirng n,int a);
26 ~People( );
27 private:
28 std::string name;
29 int age;
30 };
31 size_t People::numPeople = 0;
32 People::People(std::string n,int a:w )
33 {
34 if(numPeople > = 1)
35 {
36 throw TooManyPeople("Error:Too Many People");
37 }
38 name=n;
39 age=a;
40 ++numPeople;
41 }
42
43 People* People::MakePeople(std::string n,int a)
44 {
45 return new People(n,a);
46 }
47
48 People::~People( )
49 {
50 --numPeople;
51 }
52
53 int main(int argc,char **argv)
54 {
55 try
56 {
57 std::auto_ptr<People> p1(People::MakePeople("tom",19));
58 std::auto_ptr<People> p2(People::MakePeople("tom",20));
59 }
60 catch(std::exception &e)
61 {
62 std::cerr<<e.what( )<<std::endl;
63 }
64 return 0;
65 } |
运行结果与上面一致。这里我们控制的实例个数为1个,其实可以稍稍改动一下代码,就可以实现将控制的实例个数为N个了。
1 class People{
2 public:
3 class TooManyPeople{ };
4 static People * makePeople( );
5 static People * makePeople( const People& rhs);
6 ...
7 private:
8 static size_t numPeople;
9 static const int maxPeople ;
10 People( );
11 People(const People & rhs);
12 }
13 size_t numPeople = 0;
14 const int maxPeople=5;
15 People* People::makePeople( )
16 {
17 return new People( );
18 }
19 People* People::makePeople(const People rhs)
20 {
21 return new People(rhs)
22 }
23 People::People( )
24 {
25 if(numPeople>=maxPeople)
26 {
27 throw TooManyPeople;
28 }
29 ...
30 ++numPeople;
31 }
32 People::People(const People& rhs)
33 {
34 if(numPeople>=maxPeople)
35 {
36 throw TooManyPeople;
37 }
38 ....
39 ++numPeople;
40 }
41 People::~People( )
42 {
43 --numPeople;
44 } |
那么上面一段代码就可以很好完成控制类的实例化数目了。但是我们还可以进一步扩展,如果代码要求有大量类需要控制类的实例化数目,那么我们需要一遍又一遍的编写上面类似的代码,这样我们不累吗?其实我们可以写一个控制类实例化的模版基类,需要控制实例化的类继承该模版基类即可。
1 #include <iostream>
2 #include <stdexcept>
3 #include <memory>
4 template <typename T>
5 class tooManyPeople:public std::exception
6 {
7 public:
8 TooManyPeople(std::string e)
9 :errorMsg(e)
10 {}
11 ~TooManyPeople( ) throw( ) {}
12 const char * what( ) const throw( );
13 private:
14 std::string errorMsg;
15 };
16
17 const char* TooManyPeople::what( ) const throw( )
18 {
19 return errorMsg.c_str( );
20 }
21
22 tempalte <typename T>
23 class counted
24 {
25 public:
26 static int objectCount( ) { return numObject; }
27 protected:
28 counted( );
29 counted(const Counted& rhs);
30 ~Counted( ) { --numObject; }
31 private:
32 static const std::size_t maxObject;
33 static int numObject
34 void init( );
35 };
36
37 template<typename T> int count<T>::numObject=0;
38
39 template<typename T> const std::size_t counted<T>::maxObject=2;
40
41 template<typename T> counted<T>::counted( )
42 {
43 init( );
44 }
45
46 template<typename T> counted<T>::counted( const counted& rhs)
47 {
48 init( );
49 }
50
51 template<typename T> void counted<T>::init( )
52 {
53 if(numObject >=maxObject)
54 {
55 throw tooManyObject("Error: Too Many Object!!");
56 }
57 ++numObject;
58 }
59
60 class People:private Counted<People>
61 {
62 public:
63 static People * makePeople( );
64 static People * makePeople( const std::string n,const int a);
65 static People * makePeople(const People& rhs);
66 ~People( );
67 using Counted<People>::objectCount;
68 private:
69 People( )
70 :name(""),age(0)
71 {}
72 People(const std::string n,const int a)
73 :name(n),age(a)
74 {}
75 People( const People & rhs);
76 private:
77 std::string name;
78 int age;
79 }
80
81 People::People(const People & rhs)
82 {
83 name=rhs.name;
84 age=rhs.age;
85 }
86
87 People* People::makePeople( )
88 {
89 return new People( );
90 }
91
92 People* People::makePeople( const std::string n,const int a)
93 {
94 return new People(n,a);
95 }
96
97 People* People::makePeople(const People & rhs)
98 {
99 return new People(rhs);
100 }
101
102 int main( int argc,char ** argv)
103 {
104 try
105 {
106 std::auto_ptr<People> p1(People::makePeople("tom",16));
107 std::cout<<"The number of object is"<<People::counted<People>::objectCount( )<<std::endl;
108 std::auto_ptr<People> p2(People::makePeople( ));
109 std::cout<<"The number of object is"<<People::counted<People>::objectCount( )<<std::endl;
110 std::auto_ptr<People p3(People::makePeople("sim",15));
111 }
112 catch(std::exception &e)
113 {
114 std::cerr<<e.what( )<<std::endl;
115 }
116 return 0;
117 } |
运行结果为:
这里说明一下为什么是私有继承,因为这里使用着是不需要关心这个Counted这个计数基类,所以这里采用私有继承是最好的方式。
|