C++ ostream与printf比较

这两天与一位网友就C++流与printf函数的问题吵了两天,有点儿火药味儿(http://www.cppblog.com/converse/archive/2010/07/06/119427.html),其实我是对他一个大标题“C++的流设计很糟糕”,是比较生气的,这么多年还没有谁敢对C++的标准库如此出言不逊。我还使用百度跟GOOGLE验证了这一点,把“C++ 流 糟糕”三个关键词放在一起,GOOGLE与百度出奇一致的结果就是头条就是上面链接那博文了。 
C++标准库,可以说为了保证其通用性和效率,真是绞尽脑汁、C++特性无所不用其极,而且还专门为了设计容器增加了模板的支持。C++的流当然也不是十全是美,这也跟C++的特性本身有关,还谈不上“糟糕”,更不能说“很糟糕”! 
    原因在于 
ostream<<”str “<<1<<”, str”<<2; 
这种方式使得ostream语句何时结束,对于日志、网络等一些自定义输出操作,就不知道何时应该实施真实的操作。另一个问题就是不能保证该语句的原子操作。其实这些问题可以通过一定的手段来解决。 
1) 结束符。可以通过一个特殊的字符来解决,如标准库中的std::ends,其实就是一个字符"/0",而对于二进制流,这样恐怕是不行的,可以通过下面的方法来处理。

#include 
#include 
#include 
 
using namespace std;
 
struct streamend { };
 
class LoggerStream : public stringstream //public std::ostrstream 
{
public:
	static streamend end;
	~LoggerStream( ) {	DoPrint();	}
 
private:
	void DoPrint( void )
	{
		cout<str();	// real string
		this->str("");
	}
private:
	friend ostream& operator<<(ostream& os, const streamend& end);
};
 
streamend LoggerStream::end;
 
ostream& operator<<(ostream& os, const streamend& end)
{
#if 1         // 两种方法都可以,后者要求对RTTI的支持
	ostream* pos = &os;
	LoggerStream* pls = dynamic_cast(pos);
	if( pls != NULL)
		pls->DoPrint();
#else
	if( typeid(os) == typeid(LoggerStream))
		((LoggerStream*)(&os))->DoPrint();
#endif
	return os;
}
#define LOG( content )  lstream<<content<<loggerstream::end 保证结束符被输入="" 

int main() 
{
	LoggerStream lstream; 

 
	lstream << 1 << " hello world/n"<<loggerstream::end;   
	lstream <<"line 1"<<loggerstream::end<<" nline="" 2="" n"<<loggerstream::end;="" 

 
	LOG( "str1"<<1<<"str"<<2);              
	stringstream ss;
	ss<<"hello"<<loggerstream::end;  ="" 此处loggerstream::end不起任何作用
	return 0;
}</loggerstream::end;  >

2)多线程支持。其实也可以通过扩展LOG宏来处理,假设为自定义类型增加互斥锁,分别使用Lock( ), Unlock( )方法进行上锁与解锁,这样可以将宏修改为

#define LOG( content )  do{ /
	lstream.Lock(); /
	lstream<<content<<loggerstream::end; 
	lstream.Unlock( ); /
	}while(0)

既然说到printf与流,不妨也比较一下两个的优缺点。 
1. 先说printf的优点,也就这一点了,那就是代码简洁,格式化方便,可以在格式化字符串里一次性将输出格式化。而ostream则需要一段一段地拆分,显得比较烦锁,特别是自定义输出类型的格式时,如格式化输出浮点的小数位数、十六进制输出等,用ostream更烦锁。 
2.ostream类型安全,而printf则不能保证类型安全。 
2.1)printf容易产生输出格式字符串错误。 
int i = –1; 
std::cout<<i;
printf(“%u”, i): 
使用printf的输出结果将是错误的,虽然“那谁”网友也说到GCC中对__attribute__的扩展,可以检测printf的格式化字符串,但对于%u仍然无可奈何,而且__attribute__移植性不好,其它的编译器不支持该特性。虽然一开始写的时候可以保证格式字符串的一致性,但谁能保证在一个大系统中,哪天unsigned变量不会被修改成signed,这样输出将不再正确。

2.2)printf类型错误时会造成程序崩溃。因为在64位主机上指针为8字节,而在32位系统中指针为4字节,如果使用格式符不当,会导致地址非法访问导致程序崩溃。而且,当printf提供的参数少于格式符时也会导致指针的非法访问,导致程序崩溃(GCC -Wall会给出警告) 
2.3) printf与string混用容易出错。printf是C的API,如果使用%s直接输出string变量,将有可能导致程序崩溃(VC做了非标准的处理,可以正确输出)。 
3.printf需要记很多格式字符,而使用ostream则不需要。 
4.当printf后跟的参数很多时,很容易将参数的顺序搞错,而使用ostream则不容易出现这种情况。 
5.使用sprintf等类似函数时需要自己处理缓冲区,处理不当容易产生缓冲区溢出,导致不可预知的错误。 
6. 效率。效率应该差不多,虽然cout调用函数次数比较多,但它不用解析格式字符串,效率应该并不多。在VC2010上测试即差别比较大,printf约是cout效率的10位以上,而在Cygwin下使用gcc 4.3.4测试则相差不多,printf效率约为cout的1.2倍左右,看来应该是VC库的效率问题。下面是测试代码:

		int repeat = 2000;
		long long ll = 1;
		long l = 2;
		short s=4;
		char c = 'c';
		float f = 5.5f;
		double df = 6.6;
 
		clock_t start = clock();
 
		for(int i=0; i<repeat; ++i){
			cout<<"ll="<<ll<<", l="<<l<<" ,="" i="<<i<<" s="<<s<<" c="<<c<<" f="<<f<<" df="<<df<<'/n';
		}
		clock_t end = clock();
		clock_t elapse_cout = end - start;
 
		start = clock();
		for(int i=0; i<repeat; ++i){
			printf(" ll="%lld,">				,ll, l, i, s, c, f, df);
		}
		end = clock();
		clock_t elapse_printf = end - start;
 
		cout<<"------------------- finished --------------------------"<<endl
			<<"elapse_cout = "<<elapse_cout<<endl
			<<"elapse_printf = "<<elapse_printf<<endl;< pre="">

“那谁”网友一再不承认这是“解决”方案,认为是对问题的“规避”措施;一再强调说是为了说明C++流设计有缺陷,极其推崇GCC的__attribute__检查制机制,但事实证明它并不能解决问题,对于"%u”输出格式不能保证数据正确个输出,而且不能给出警告,所以很容易导致输出数据的错误。如果想彻底解决问题,通过C++标准,严格定义格式输出,并在编译期进行强类型检查,这但样做不太合适,至少不适合定义到编译器的标准中去,因为printf只是一个库函数一样,语言不可能因为某个函数制定标准。 boost::format和fastformat也都是通过格式字符串来解析,无论功能如何强大,都不可能在编译器层面解决类型检查的问题,输出错误也就很难避免。

流操作符只是一个普通的运算符,它不可能预测未来如何使用它,不可能有办法在语法上强制用户多调用它一次,不仅C++做不到,任何一种语言也都不可能做到。如果要彻底解决格式化的问题,但是可以通过RTTI来实验,这就得改变C++的变参数机制,参数列表不能使用指针。C#和java使用数组可以很好地解决这个问题,因为所有的对象都可以通过object引用来传递,保持了原对象的类型信息,而在C/C++中是不可能的。如果哪天C++标准也定义一种所有类型的超父类型object的话,这个问题就迎刃而解了。

ostream毕竟是新生事物,也凝聚了很多人的心血,牺牲了一点效率,更好地保证软件的正确性,减小由于程序员的疏忽而产生的错误,特别是严格的类型检查,保证软件输出的正确,而printf的弱类型很难保证输出的正确。鉴于软件的最基本需求----正确性,建议使用ostream替代printf。那一点点的性能差距,早已经被今天强大的硬件给弥补了。

FROM: http://blog.csdn.net/nocky/article/details/6056808

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值