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<
int main()
{
LoggerStream lstream;


lstream << 1 << " hello world/n"< lstream <<"line 1"<

LOG( "str1"<<1<<"str"<<2);             
stringstream ss;
ss<<"hello"< return 0;
}

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

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

既然说到printf与流,不妨也比较一下两个的优缺点。
1. 先说printf的优点,也就这一点了,那就是代码简洁,格式化方便,可以在格式化字符串里一次性将输出格式化。而ostream则需要一段一段地拆分,显得比较烦锁,特别是自定义输出类型的格式时,如格式化输出浮点的小数位数、十六进制输出等,用ostream更烦锁。
2.ostream类型安全,而printf则不能保证类型安全。
2.1)printf容易产生输出格式字符串错误。
int i = –1;
std::cout< 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 cout<<"ll="< ,ll, l, i, s, c, f, df);
}
end = clock();
clock_t elapse_printf = end - start;

cout<<"------------------- finished --------------------------"< <<"elapse_cout = "< <<"elapse_printf = "<

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

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

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

你可能感兴趣的:(C++,c,object,gcc,Google,编译器)