按照惯例,我们简单回顾一下上次的问题。上次最后说,“ 所有的这些问题,在组合的情况下,也会发生吗?在对象数组初始化时,如果我们希望每个对象的初始化状态都不同,应该如何做?”,关于组合的情况,我就不回答了,希望您测试过了。但对象数组的初始化,却很有趣。
既然要合适的默认构造函数才能初始化,又要每个对象的初始化状态都不同,这就有些为难了。我们有个思路是,在这个合适的构造函数里:),使用键盘输入初始化每个对象。
则把两个类的相应构造函数分别改为:
//构造函数1
Shape::Shape(void)
{
std::cout<<"\nInput x,y(int):";
std::cin>>x>>y;
}
//构造函数1
Window::Window(void):Shape()
{
std::cout<<"\nInput width,height(int):";
std::cin>>width>>height;
}
如此您不难想象程序执行的过程。
但是,有一个问题是,如果对象数组的规模比较大,这种输入的方式比较惹人厌的:)。留个问题,您可以找到另一种高效的初始化方式吗,能满足我们这里的要求?
我们今天要谈的是,特殊需要的初始化,以实例对象须唯一化为例子。
初始化工作是我们系统启动的第一步,我们的需要是各种各样的。比如,有时,类的实例化要求只能有一个对象(注意,不是我们程序员只实例化一个对象,而是类的机制使你只能实例化一个对象!)。
这样一个问题,对于初学者,常有这样的思路:
首先,类实例化时,同时对对象个数进行计数,在主函数中(或使用类的环境中)写控制逻辑使得对象只能建立一个。不说是否能实现,这个把类自己的使命交给外部逻辑来完成控制的思路肯定是不可取的。它与oo基本理念是相违背的!
其次,更多的主张这样实现。在类的实现上,内定义对象计数器(整型静态成员变量),然后在构造函数中,进行计数操作。同时判断计数器值,如果大于1,则退出不构造对象,这样自然就达到了只能实例化一个对象的要求。
这种思路听起来有点意思。但是,事实上怎么样呢?我们来实现看看。我们的例子是,要求类Library2,
只能有一个运行实例。
尝试类的定义:
#pragma once
class Library2
{
static int count;//对象计数器
char book[20];
public:
Library2(char _book[]);
virtual ~Library2(void);
char * GetBook(void);
static int GetCount(void);
};
#include "libray2.h"
#using <mscorlib.dll>
//#include <stdlib.h>
#include <string.h>
int Library2::count=0;//初始化为0
//这里是思路的关键
Library2::Library2(char _book[])
{
if(count==0)
{
strncpy(book,_book,sizeof(book));
count++;
}
else return;//(1)如何退出?return?exit?还是其它?
}
Library2::~Library2(void)
{
count--;
}
char * Library2::GetBook(void)
{
return book;
}
int Library2::GetCount(void)
{
return count;
}
整个思路的关键,是在于(1)代码的实现。问题是,“则退出不构造对象”,如何才能做到?您来看看,上面的“return”能否做到?换为“exit(0)”呢?您要不能一口拿准,就回头再看看,好好想一下:)。
答案是比较显然的,“return”的作用,无异于调用什么都不做的构造函数,对象其实还是创建了的,内容是地址恰所含的值。只是,count没有来得及记录。用“exit(0)”呢?退出进程,也不是我们想要的。您可以测试一下,便会明白代码的作用。
继续沿用这个思路,我们还有“异常机制”没有使用。既然类Library2只能有一个运行实例,当程序员试图建立多于一个的对象时,我们通知他一个异常发生:你定义的对象个数超过了我们的类的要求。
对上面的代码做如下的改动:
#pragma once
class TooManyObj{ }; // 异常类:Library2对象个数大于1时抛出异常
class Library2
{
static int count;
char book[20];
public:
Library2(char _book[]);
virtual ~Library2(void);
char * GetBook(void);
static int GetCount(void);
};
//...
Library2::Library2(char _book[])
{
if(count==0)
{
strncpy(book,_book,sizeof(book));
count++;
}
else throw new TooManyObj;//(1)这里用异常来控制
}
//...
或许这样就可行了呢,我们赶紧来测试这个版本的类Library2。测试文件代码为:
#include <iostream>
#include "libray2.h"
void main()
{
try
{
char *name="C++ Primer";
Library2 library(name); //第一次实例化
std::cout<<"Test:library's book:\n"
<<library.GetBook()<<"\n";
name="Thinking in C++";
Library2 library2(name); //第二次实例化:我们渴望这里由异常发生
//但下面的逻辑部分将受什么影响?
std::cout<<"Test:library2's book:\n"
<<library2.GetBook()<<"\n";
std::cout<<"\nLibrary2 has object(number):"<<Library2::GetCount()<<" .\n\n";
}
catch(TooManyObj *e) //处理异常
{
std::cout<<"\nException of TooManyObj...\n";
}
}
执行,结果是:
Test:library's book:
C++ Primer
Exception of TooManyObj...
Press any key to continue
很遗憾的是,当异常发生后,就直接转到异常处理部分了。我们还有许多正常的代码,根本没有机会执行。“则退出不构造对象”,这样的简单一句话,因为它违背了初始化的机理,而刻意去追求它,是多么的费周折。而且,我们总是不由自主的把一部分的逻辑控制寄托于类的使用者来做,这本身就应该避免。
那么,正确的解决方案是怎样的呢?
答案是我们需要用到一个模式Singleton。请原谅我这么不避其繁的写帖子,因为我觉得,我们遇到问题,总是一步步接近答案的---足够细心的审视这个过程会使得我们更有能力面对新问题。虽然模式本身只是经验的总结,但在经验取得之前,总要有它的探索过程。
我们重新来审视问题,是“要求类的实例化只能有一个”,很显然,如果对象可以直接创立的话,如这样:
Library2 library(name);
我想我们已经没有机会在保证程序不受影响的情况下能合理的“则退出不构造对象”。那么,到这里,我们似乎无路可走了!
想一想我们的类,oo特点,难到不能柳暗花明吗?
哈哈,那我就把构造函数也封装为保护的,不让你直接创立对象。是的,这样或许会可以,我再想想;既然构造函数封装起来,那么我们要生成的对象,肯定不是这个形式:
Library2 library(name);
那是什么形式呢?也只有:
Library2 *plibrary;
了。这样“里外”都有了,重点是“中间”:),应该是有某个公共成员函数来初始化指针(你想想,这个函数的调用只能靠类本身了,那就是静态的),又要保证无论几个这样的指针,它们只能指向同一个对象的地址。如何办?静态变量和静态函数又要大显作用了。是的,我们定义一个静态的指针变量(指向类对象)作为私有成员变量,然后定义一个静态的成员函数,公共的接口。如果静态的指针变量为空,就初始化一个对象给它,返回,否则,把它直接返回。这个静态的成员函数,不是可以作为解决方案的最精彩的一笔?
看这个模式的实现代码:
#pragma once
class Library
{
private:
static int count; //仅为了验证用
char book[20];
static Library *_instance;//静态指针变量
protected:
Library(void); //构造函数
virtual ~Library(void);
public:
static Library * Instance(void);//这里才是美妙的
void SetBook(char _book[]);
char * GetBook(void);
static int GetCount(void);
};
#include "library.h"
#using <mscorlib.dll>
#include<string.h>
int Library::count=0;
Library *Library::_instance=0;
Library::Library(void)
{
count++;
}
Library::~Library(void)
{
count--;
//
delete _instance;
}
//简单的代码,优美的思路,解决了我们的问题
Library * Library::Instance(void)
{
if(_instance==0)
{
_instance=new Library;
}
return _instance;
}
void Library::SetBook(char _book[])
{
strncpy(book,_book,sizeof(book));
}
char * Library::GetBook(void)
{
return book;
}
int Library::GetCount(void)
{
return count;
}
测试代码有可能是这样的:
//...
char *name="C++ Primer";
Library *plibrary;
plibrary=Library::Instance();//看类的定义:这里发生了什么?
plibrary->SetBook(name);
std::cout<<"Test:plibrary's book:\n"
<<plibrary->GetBook()<<"\n";
Library *plibrary2;
plibrary2=Library::Instance();//看类的定义:这里又发生了什么?
std::cout<<"Test:plibrary2's book:\n"
<<plibrary2->GetBook()<<"\n";
std::cout<<"\nLibrary has object(number):"<<Library::GetCount()<<" .\n\n";
//...
您再仔细的看一下这个模式,仔细琢磨它的美妙。呵呵,原来我们写程序,也可以这么的过瘾!如果您对严格的文档感兴趣,且想了解更多的模式,当然,我推荐您去看“四人帮”那本关于模式的书了:)。
总的来说,我们的初始化工作,是件十分值得关注的工作。这四次的帖子,小结一下,主要谈了:
第一次,好的初始化过程与静态成员的初始化;
第二次,一些变量只有唯一的初始化形式,通过例子,告诉您要特别注意。然后,一步一步,来看资源浅拷贝的问题。
第三次,组合与继承中的初始化问题,最后再说明对象数组初始化需要注意的地方。主要是继承的例子。我想澄清一些想法,强调一些观念。
第四次,特殊需要的初始化,以实例对象须唯一化为例子。
这些,也只是展露一角,但希望能抛砖引玉,引起您对之的思考与关注。如果能对您有所帮助,我心里将是十分高兴的。
最后,谢谢大家的关注。