首页 > 代码库 > 流和几条设计准则

流和几条设计准则

时间:2014.05.27

地点:基地

---------------------------------------------------------------------------------------

一、关于std::cin和std::cout

  cin和cout的类型是std::istream和std::ostream 。这两个类型又分别对应着 

std::basic_istream<char>
std::basic_ostream<char>
即,是分别是这二者typedef后的别名而已,当然,这里省略的模板的默认参数,完整来说是:

std::basic_istream<char,std::char_traits<char>>
std::basic_ostream<char,std::char_traits<char>>

---------------------------------------------------------------------------------------

二、流的rdbuf()成员函数

  流的rdbuf()成员函数返回某个流对象说使用的streambbuf指针,该指针直接指向流底层使用的streambuf,正如前面一篇博文所说,绕过上层直接操作流的底层非常高效。另外,我的操作符operator<<()也接受这样一个指向streambuf的指针,于是对于某些操作,就像把一个池子的水端起来直接倒往另外一个水池一样,比起架起水管来让细水漫流是不是很爽?

---------------------------------------------------------------------------------------

三、应用

  假设我们要写一个ECHO程序,简单的相应输入,能从cin获得输入,然后将输出发送到cout,也能从一个名为infile的文件获得输入,输往outfile文件。

有一条设计准则:

  尽量提高可读性,避免撰写精简代码,避免晦涩。因为简洁代码难以理解和维护。

还有一条设计准则

  尽量提高可扩充性

通过分析,我们知道,从获取输入到输出这个过程中,我们还可能要对数据进行一些处理,比如将字符进行大小写转换,删除一些字符,字符统计等,因此,我们需要将这一任务独立出来。

于是又有一条设计准则:

  尽量提高封装性,将关系分离。

先来看整个程序框架:

int main(int argc, char* argv[])
{
	using namespace std;
	fstream in, out;
	if (argc > 1)
		in.open(argv[1], fstream::in, fstream::binary);
	if (argc > 2)
		out.open(argv[2], fstream::out | fstream::binary);
	Process(in.is_open()?in:cin,out.is_open()?out:cout);
	return EXIT_SUCCESS;
}
现在的问题是怎么去实现Process函数,我们将处理数据的任务交付给它,它应该能够处理不同的参数类型,我们想到的是多态,C++中有四种方式可获得多态行为:虚函数,模板,重载,转换。

3.1模板(编译时多态)

  通过模板实现多态也叫编译时多态,提供有被传递对象的合适接口,实现如下:

template<typename In, typename Out>
void Process(In& in, Out& out)
{
       ....一些数据处理,
	//或者只是简单的这样:out << in.rdbuf();
}

3.2虚函数(运行时多态)

  通过虚函数的方式的多态也叫运行时多态,它声明的参数类型是一个具有可能的接口们对应的公共基类即可。比如一种可能的实现如下:

void Process(basic_istream<char>& in,basic_ostream<char>& out)
{
  //...执行某些操作
  //或者只是这样 out<<in.rdbuf();  //把一池子水直接倒入另外一个池子
}
这种方式具有一定的依赖性,它要求输入和输出必须分别从basic_istream<char>和basic_ostream<char>派生。(我们的istream,ostream,iostream,ifstream,ofstream,stringstream等都分别从二者派生而来,可如果不是呢,比如宽字符流是基于wchar_t的。或者有些流有着用户自定义的traits。于是为了提高可扩充性,应该使用模板,让编译器去推导这些合适的参数

template<typename C,typename T>
void Process(basic_istream<C,T>& in,baise_ostream<C,T>& out)
{
       ......
}

但显然第一种方式比较看起来舒服。只要可能,一段代码一个函数或一个类,应该集中只知道和负责处理一件事。比如这里,程序包括两部分,一部分代码知道输入/输出源和目标中的可能区别,到底使用什么样的流,另一部分知道如何真正的执行处理。通过将这两部分分离,使得代码用途更加清晰,易于理解和阅读。

---------------------------------------------------------------------------------------

四、总结

  避免写出来的代码只能解决当前问题,几乎任何时候若能写出可扩充的方案,都是更好的选择。但在编写专用代码只解决当前问题(短视,难以扩充)和编写一个宏大的通用框架去解决本来很简单的问题(追求过度设计)之间要自己琢磨取得最佳平衡。第一种方法简单而灵活,更能适应新要求,没有束缚,不仅仅只能和iostream体系打交道。在函数或类的设计中,我们应该尽量考虑可扩充性。稍微多去思考下,当前正在解决的问题是不是某个更通用问题的特例,于是应该还多做一点工作,而不要仅仅满足于当前。