那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

C++的流设计很糟糕

最近需要提供一个功能,采用类似C++流输出的格式输出一些日志信息, 例如Log(FATAL) << "log to" .

我找了两个类似项目来研究,google的gloglog4cpp, 它们都支持以C++流输出格式进行输出.

但是研究到最后,我发现最大的问题是, 如果按照C++的流输出格式进行输出, 将无法判定需要输出的信息到哪里是结束.比如log << "hello " << "world",是无法判断到底在输出"hello"还是"world"的时候上面的参数输入已经结束了.上面两个项目中, 解决这个问题的办法大致是相同的,以下面可编译运行代码为例说明它们的做法(在linux g++下面编译通过):
#include <iostream>
#include 
<sstream>

#ifdef __DEPRECATED
// Make GCC quiet.
 # undef __DEPRECATED
 # include 
<strstream>
 # define __DEPRECATED
#else
 # include 
<strstream>
#endif

using namespace std;

class LoggerStream : public std::ostrstream {
 
public:
  LoggerStream(
char * buf, int len)
   : ostrstream(buf, len),
    buf_(buf),
    len_(len) {
  }

  
~LoggerStream() {
    
// do the real fucking output
    cout << buf_;
  }

 
private:
  
char *buf_;
  
int len_;
};

int main() {
  
char buf[100= {'\0'};

  LoggerStream(buf, 
sizeof(buf)) << 1 << " hello world\n";

  cout 
<< "buf = " << buf << endl;

  
return 0;
}

在上面的代码中, 开始进行输出的时候首先初始化一个LoggerStream对象, 而在输出参数输入完毕的时候将调用它的析构函数,在这个析构函数中才完成真正的输出动作.也就是说,由于对输入参数结束位置判断手段的缺失,C++中不得不采用这个手段在析构函数中完成最终的输出工作.
这样的做法,最大的问题是,频繁的构造/析构开销大,而且每个"<<"操作符背后又需要调用ostream的operator<<,也就是假如你的输入参数有三个将调用operator <<三次(当然是经过重载的,不一定都是同一个operator<<),因此,假如需要考虑多线程的话,那么一次输入有多个函数函数中被调用,仍然是问题.天,要使用这门语言写出正确的程序来,需要了解底下多少的细节呢?!

最后,我向项目组反映这个问题,一致同意以C中类似sprintf可变参数的形式实现这个功能.可变参数解决这个问题,就我的感觉而言,就是输入参数的时候,稍显复杂,需要用户指定输入的格式.然而,其实这个做法也有好处:作为函数的使用者,你必须明确的知道你在做什么并且反馈给你所使用的函数.明确的,无歧义的使用函数,而不是依靠所谓函数重载猜你的用意,我想也是避免问题的一个手段.gcc中, 提供了对可变参数检查的机制,见这里.

posted on 2010-07-06 13:04 那谁 阅读(14646) 评论(79)  编辑 收藏 引用 所属分类: C\C++

评论

# re: C++的流设计很糟糕  回复  更多评论   

你为什么要了解细节呢,你只需要知道无论什么类型只要编译通过都能operator<<就行了。而且结束很好判断,自己写一个全局变量:

class MyEndLine
{
} myEndLine;

重载operator<<(MyStream&, const MyEndLine&)

最后
myStream<<1<<2<<3<<MyEndLine。

解决。

可变参数什么的,没有讨论的余地,新代码绝对不能用。除非你确信你、你的同事或者你的手下的水平都高超到用可变参数而绝对不会产生内存问题譬如说溢出啊之类。
2010-07-06 13:52 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

楼主,批C++可以,但你为什么要这样批呢?

# re: C++的流设计很糟糕  回复  更多评论   

使用C++类库的大忌之一:通过阅读代码而不是写测试来了解功能
使用C++类库的大忌之二:如果有文档/demo,也非得通过阅读代码而不是写测试来了解功能
使用C++类库的大忌之三:通过阅读代码而不是写测试来获得性能上的数据

总之:非必要情况下,如果你非得了解代码才能知道一个类库怎么用,而且如果类库的文档/demo还很好的话,那绝对是你自己的问题。

一般来说,质量好的C++的类库都做到,编译通过了一般都不会有【大】问题,就算有了问题也会被assert出来的。
2010-07-06 13:54 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
嗯,你说的那种方式 确实也是个办法吧.
2010-07-06 13:55 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
所以为了项目的安全,我还是建议你不要使用可变参数
2010-07-06 13:56 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
C++的设计里面 编译器做了太多额外的事情 以至于你要用好这么语言不得不去多了解细节 我觉得这是很糟糕的地方 因为你需要付出很大的代价才能对这门语言有足够的了解.
2010-07-06 14:03 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
我想了想 你这样还是有问题的,比如一次输入几个参数 将会在几次函数调用中完成 如果我需要做到是多线程的 这一点如何保证呢?还是类sprintf那样的在一个函数中搞定所有的事情吧.
2010-07-06 14:11 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

鲜明的对比:

学会了errno就学不会exception。
学会了prints就学不会streams。
学会了C就学不会C++。
。。。

学会了errno继续学exception。
学会了printfs继续学streams。
学会了C继续学C++。
。。。
2010-07-06 14:16 | OwnWaterloo

# re: C++的流设计很糟糕  回复  更多评论   

#define LOG_PUTS(log, record) \
do{ \
std::ostringstream os; \
os << record; \
lg->puts(os.str().c_str(), os.str().length()); \
}while(0)


这样不就行了吗
2010-07-06 14:22 | cui

# re: C++的流设计很糟糕  回复  更多评论   

既然是写log,你不会想要调用一次operator <<就真的写一次设备吧?那可是IO速度差着几个数量级的设备呢(C++的标准库也是用了buffer,不信你可以去测)

否则这样的操作效率何在?xxx << 1 << 2 << 3 << 4 << 5 << 6 << 7;

熟悉C的话,可以这样理解:一次<<其实就是添加一个sprintf中的参数

建议在碰到endl再真的向物理设备输出。这样的话thread safe很容易做到——如果不这样的话,多线程一起输出log,即使thread safe了,log也没法看了

# re: C++的流设计很糟糕[未登录]  回复  更多评论   

兄弟, 你走远了吧. log4cpp的流使用方式就很好啊,类似如此:

#define SLOG(Level,Event) \
do{ \
std::ostringstream _SLOG_BUF_INTERNAL_; \
_SLOG_BUF_INTERNAL_<<Event; \
printf_log(Level,_SLOG_BUF_INTERNAL_.str().c_str()); \
}while(0)
2010-07-06 14:28 | cppexplore

# re: C++的流设计很糟糕[未登录]  回复  更多评论   

@cui
......................惊人的一致............
2010-07-06 14:29 | cppexplore

# re: C++的流设计很糟糕  回复  更多评论   

我写过一个小的日志库,已经在产品中使用过,
http://code.google.com/p/cute-log/
2010-07-06 14:55 | cui

# re: C++的流设计很糟糕  回复  更多评论   

最大的问题是,频繁的构造/析构开销大
这个应该可以通过单例模式解决吧
你这篇文章是不是主要想说明输出多个参数的时候会多次调用“<<”,
而使用“sprintf”效率会比较高,是这样么?
2010-07-06 15:00 | joewan

# re: C++的流设计很糟糕  回复  更多评论   

是LoggerStream写得有问题,如果你要这样写,可以考虑写一个类似endl的操作子来做真正的输出
2010-07-06 16:25 | t

# re: C++的流设计很糟糕  回复  更多评论   

记得上次看你一篇博文,google的c++编程规范里面规定了不准使用c++的流,全部用printf一族。对于蹩脚的东西干脆不用,很好。
2010-07-06 17:03 | 赵建寅

# re: C++的流设计很糟糕  回复  更多评论   

linux kernel,apache,lighttpd,nginx,mysql,postgresql都是纯C写的(mysql中有少量C++代码,在非核心的部分)。C++程序员应该反思。C++的某些语言特性在诱导程序员作出不合理的设计。。。KISS是王道。
2010-07-06 21:00 | c-programmer

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
另外,还有个问题,很难在编译语法层面保证你的最后一个输入是那个标记类吧....
2010-07-06 22:26 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

to cui and cppexplore
我们看的不是一个地方,呵呵.
2010-07-06 22:35 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

C++ 流其实就是一个文件流,其实当初设计C++ 的人,大多数是混口饭吃的,当然,他们的责任心是有的,但是责任心跟专心是有区别的,爱好者很多时候,比专业人士对于该专业更加用心。
2010-07-06 22:43 | 飞鸽传书

# re: C++的流设计很糟糕  回复  更多评论   

能用C方式解决的就用C,该用STL的就用STL。
我觉得如何发现蹩脚,那就是选择的不对。或者是使用的不对。
2010-07-06 22:52 | xx

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
为什么说是大忌呢?
2010-07-07 02:40 | 夜风

# re: C++的流设计很糟糕  回复  更多评论   

C++比较适合描述数据关系
C比较适合描述流程
应该结合起来用。

不冲突。


2010-07-07 03:46 | johndragon

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
多线程输出IO信息是不对的,请修改设计。
2010-07-07 11:14 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
C++难学是难学,你见过有人抱怨过微积分太难学而说几何比微积分好的吗(初级的微积分解决的问题跟几何是一样的)
2010-07-07 11:15 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
实在不行,你可以
Write(StringBuidler<<"vczh"<<" is "<<" a "<<" programmer!");总行了吧。虽然长了一点,也总比printf好。之余语法怎么调整你自己去想就好了。
2010-07-07 11:18 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
呵呵,不知道你有没有写过服务器端程序,多线程同时写log是肯定会存在的.
至于说的sprintf有缓冲区溢出问题,也可以有做法进行避免.
总之,我的结论是C++的流在判断输入结束方面存在缺陷,至于后面跟的帖子写的其他格式,则不是我关注的重点了,我这篇文章只为了说明C++的这个缺陷,这是我写这篇文章的目的.

另外,你那个微积分和几何的比喻放在这里不妥,两者不能解决相同的问题,不属于一个类型.
2010-07-07 11:24 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
我觉得不是需不需要多线程log的问题,而是实际上在写的线程只能有一个。举个例子,你有一个log线程,暴露一个队列,其他线程往里面填,然后log线程去写文件。

C++难并不是缺陷,其实每一个细节都规定的十分清晰。而且C++那些也不是毫无规律的,C++试图做到每一个语法你都可以去写callback,应该好好理解这一点。
2010-07-07 11:44 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
变量创建销毁有callback(构造函数析构函数),复制有callback,操作符有callback(可以重载),等等。当然我认为构造函数和析构函数是非常简单的,复杂的是overloading,一般来说我很少使用。实际上最复杂的是模板,这个你不反对吧,而且我认为你们也应该不会使用模板,或者仅仅使用模板的低级形式。因此抛开这些之后,我不知道你认为还有什么地方是“复杂”的。如果你说的是stl复杂,那显然跟C++没关系。
2010-07-07 11:46 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
"其实每一个细节都规定的十分清晰"
我还真没有觉得,也许是真的,但是要做到使用者也非常清晰,代价很大.就学一门语言的代价而言,我觉得过大,因为语言不是全部,还有很多需要学的,如果过分多的把精力放在语言学习上,我觉得有点本末倒置.所以,我现在只使用那些我清楚的,有把握的C++特性,你可以说我保守,但是我不是学生,没多少时间花在语言学习上.
2010-07-07 11:47 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@陈梓瀚(vczh)
也有用模板,用模板创建callback,但是我不了解做法,只了解怎么使用,模板让我很崩溃,一直不想深究.
2010-07-07 11:48 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
我开了个新的首页的文章,我们挪去那里好了,你可以把你认为受不了的地方列出来,然后我们讨论讨论。当然最好说的详细一点,这样有助于双方的理解。
2010-07-07 11:54 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

C++语言本身没有任何问题,是你掌握得不是很熟练。
C++作为语言,只是一种表达工具,而一个设计的好坏跟语言本身是没有任何关系的,只要有思想,C++可以做出非常好的设计的。
之所以以怀疑流的使用,只是你还没有理解流,还没有了解如何学得更好。
别动不动就批C++,还没有理解它怎么能知道它不好呢?
C++难是难,学起来非常困难,难是因为它太强大,造自行车不难,学会骑自行车也不难,而造飞机与开飞机就非常难,没开好飞机摔了,能说飞机不好吗?
2010-07-08 13:06 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
那请您就事论事说一说怎么解决我提的问题,谢谢.
2010-07-08 13:08 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

说C++复杂,那是因为你没清楚如何使用她~

C++标准库的IO是挺不错的架设,其实,你或许不应该从ostrstream继承,应该从basic_ostream,然后设计自己的basic_streambuf ,相当简单实用
2010-07-08 18:04 | 陈煜

# re: C++的流设计很糟糕  回复  更多评论   

@陈煜
那请您给出可运行的代码例子并且解决我上面的问题,谢谢.


2010-07-08 18:11 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

请看
http://www.drdobbs.com/184401470;jsessionid=3I5YVJBUOELIJQE1GHPSKHWATMY32JVN?pgno=1

还有
http://www.codeproject.com/KB/debug/debugout.aspx
2010-07-08 21:39 | 陈煜

# re: C++的流设计很糟糕  回复  更多评论   

@陈煜
呵呵,我把那个codeproject的代码拉下来编译验证,正是我上面给出来的结论.麻烦你自己回头看看那份代码和我文章中的描述吧.
以那个项目的代码为例,在类basic_debugbuf的析构中调用了sync,这个函数中再调用output_debug_string输出字符.就是我文章中提到的情况:因为C++的流输出对输入参数的结束位置无法判断,只能在析构函数中做真正的输出.

另外那篇文章,太长了,我不去看了.
2010-07-08 21:55 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

为啥不使用flush呢?
2010-07-08 22:01 | 陈煜

# re: C++的流设计很糟糕  回复  更多评论   

@陈煜
行了,我证明了你说的办法不能解决我这里提的问题.就这样吧.
给他人下结论之前,麻烦你做过充分的验证,我在上面可是有给出可编译运行的程序的,谢谢.
2010-07-08 22:04 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@陈煜
另外,这个问题跟flush没有一毛钱的关系,你这么问说明你对我提出的问题还是不了解,呵呵.
2010-07-08 22:36 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

#include <iostream>
#include <ostream>
#include <sstream>
#include <strstream>

using namespace std;

struct streamend { };

class LoggerStream : public stringstream //public std::ostrstream
{
public:
static streamend end;
~LoggerStream( ) { DoPrint(); }

private:
void DoPrint( void )
{
cout<<"------- DoPrint( ) ------------"<<endl; // 只是用来证明何时调用了这个东东
cout<<this->str(); // real string
this->str("");
}
private:
friend ostream& operator<<(ostream& os, const streamend& end);
};

streamend LoggerStream::end;

// 第二个参数只是用来支持结束符
ostream& operator<<(ostream& os, const streamend&)
{
#if 1 // 0或1两种方法都可以
ostream* pos = &os;
LoggerStream* pls = dynamic_cast<LoggerStream*>(pos);
if( pls != NULL)
pls->DoPrint();
#else
if( typeid(os) == typeid(LoggerStream))
((LoggerStream*)(&os))->DoPrint();
#endif
return os;
}

int main()
{
LoggerStream lstream; // 你只需要构造一次,也可以声明为静态的
lstream << 1 << " hello world\n"<<LoggerStream::end;
lstream <<"line 1"<<LoggerStream::end<<"\nline 2\n"<<LoggerStream::end;

stringstream ss;
ss<<"hello"<<LoggerStream::end; // 此处LoggerStream::end没有任何影响
return 0;
}
2010-07-08 23:09 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
你这个办法上面已经有人说过了.请看我的回复,谢谢.
2010-07-08 23:14 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

不解决你说的问题吗?
2010-07-08 23:17 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
请问你如何从语法,编译器的角度避免用户没有输入最后那个end呢?
2010-07-08 23:19 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

我在上面已经就这个方式进行了回复,恕我不再回复.
再说一句,给他人定性下结论之前,自己先看清楚问题,和别人的回复,同时自己去验证过可行性,谢谢.
2010-07-08 23:21 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
另外,还有个问题,很难在编译语法层面保证你的最后一个输入是那个标记类吧....


这个你使用printf更容易出问题,后面几个参数在编译时是检测不出来的,运行时会致命的。
而且使用printf %d 输出long long都有可能致命

而且,你定义的变量日后修改类弄,你容易没的修改格式字符串,那样你的程序会在不必要的写日志时崩溃,而且是不确定的崩溃,在不确定平台崩溃,有的平台会死,有些平台却没事儿
2010-07-08 23:25 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
sprintf可以使用编译器的特性进行检查,gcc就可以做到.
2010-07-08 23:27 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
编译器扩展是牺牲了可移植性的,当然你也可以通过写个宏去避免这个问题


#define LOG( content ) lstream<<content<<LoggerStream::end
LOG( "str1"<<1<<"str"<<2);

这个能不能满足你的要求呢?
2010-07-08 23:41 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
行了,到此打住吧,我只想证明这个东西是确实有缺陷的.到此为止.
2010-07-08 23:41 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
至少现在C++通过自己的特性还是解决了你的问题,运算符只是个函数而已,如何去使用是程序员自己的事,C++委员会也不会接受为了一个函数调用去修改C++的特性的。这个与C++的流设计也没有什么关系,你上来就一个大标题,

“C++的流设计很糟糕”

是不是有点儿太不负责任了?
2010-07-08 23:47 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
我上面已经回复了,你那个办法怎么叫解决?不是又引入了新的问题么?
"请问你如何从语法,编译器的角度避免用户没有输入最后那个end呢?"

你的另一种做法,不是解决,叫规避,谢谢.
2010-07-09 00:45 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

说白了,你提供了这个机制,又不提供相应的检查机制,如何叫"解决"?
2010-07-09 00:47 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
别老想着让编译器解决,C++设计语言的指导思想之一就是能通过已有特性解决的问题就不进行语言特性扩展,别一门心思地让编译器来解决
编译器这么多,只要不列入C++标准,所有的扩展都是不可移植的
考虑问题是不是可以换个角度呢?
2010-07-09 09:27 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
"解决"问题应该是"自封闭"的,也就是不引入别的问题.在这里我提出的类sprintf的解决方式,带来的格式输入有误,缓冲区溢出等问题,我都有方法解决掉,这才叫"解决"问题.
你的第一种方式,带来的另外一个问题,你没有帮我"解决"掉,所以,你这不叫"解决"问题.
斗胆说一句,平时工作中,你都是这么给人"解决"问题的么?假设你是制造车的,我要解决代步问题,从你那里买辆车,如果还要担心刹车会失灵,这个能叫做"解决"问题么?

你的另一种方式,不是"解决"问题,相反,恰恰如我说的那样,是这种方式存在缺陷,你才要使用别的方式规避它,这也就反证了这个方式是存在缺陷的了.

你说到编译器不能解决所有的问题,我承认,但是要最大限度让编译器发挥作用来帮助解决问题,人的因素很不稳定,不能把项目的成败过多的放在这些不稳定因素中.

2010-07-09 10:34 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

对C++流的抱怨也不是一天两天了,老是使用C的sprintf也不能算好的办法,最好能使用类型安全的printf,boost::format是一种解决办法,还有一个fastformat也可以看一看,但是完美的设计是不存在的,还是看自己的需求吧。
2010-07-09 11:18 | wuqq

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
软件最基本要求是正确,请教如果使用printf下面这个问题如何解决?
char cc = -1;
printf("cc=%u\n", cc);
gcc 4.3.4 :
cc=4294967295

g++ -Wall 没有任何警告
2010-07-09 13:38 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
请看这里:
http://blog.chinaunix.net/u3/91522/showart_2054004.html
我的做法会在项目组内禁止直接使用printf,而使用加上了__attribute__封装的函数.
紧跟着的问题是,如何能保证禁止直接使用printf呢,define宏解决.
2010-07-09 13:47 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
1) gcc标准库的printf也是加了__attribute__选项的,也能解决大部分的格式符,但上面我的那个例子是不行的,
你可以试一下你自己写的函数

你也可以把你的函数帖出来,我帮你验证一下。

2)既然你使用自己封装的函数来打印,跟我原来定义一个宏LOG( )当函数用来比有什么优势。
2010-07-09 15:14 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
是的,我测试了一下,确实不行.查了一些文档,发现是因为C语言里面认为,以%u输出char是正确的,比如这里:
http://blog.csdn.net/wangyadong/archive/2009/05/22/4208013.aspx

我把代码贴在这里:

#include <stdarg.h>

extern void my_printf(const char *format,...) __attribute__((format(printf,1,2)));

#define printf my_printf

int main() {
char cc = -1;
printf("cc=%u\n", cc);

return 0;
}

void my_printf(const char *format,...) {
// do the really fuck output
}

使用宏替代掉系统的printf的作用是,用户可以完全不知道后面的改动,照常使用printf的功能.而你的LOG宏,只是规避了问题,没有解决我提出的log<<"hello"<<"world"无法判断结束符的问题,如果你有一种办法,可以不改变我用户的输入,而解决这个问题并且不带来新的问题,这个才算是解决吧.
2010-07-09 18:52 | 那谁

# re: C++的流设计很糟糕  回复  更多评论   

@那谁
不光是对于char型的不行,对于int、short使用%u输出也不行,这是printf最致命的问题。

你可以不承认LOG宏是解决了你的问题,但它是一个可以获得期望行为的,保证输出的正确性,而使用printf会导致程序错误的输出。

如果你非得要使用printf,给个建议,在自定义的函数内检查格式字符串,当发现有%u时,可以检测它的值是正的还是负的,如果是负的可以给个警告或者抛出异常,保证行为的正确性。

不要在“规避”还是“解决”两个词之间找别扭了,保证程序的正确性才是根本。
printf会给你提供更多的出错机会
还要程序员时刻得着各个转义字符的对应关系,我是比较懒,我是记不住
而且__attribute__也只有GCC在新版本中扩展功能才有,其它编译器是不支持的
使用printf还会影响到使用std::string,这两个混着用还经常会忘了调用.c_str()方法,这样在有些编译器是安全的,但很多编译器也会产生致命的错误
使用printf如果参数比较多的话还容易造成顺序错误
2010-07-09 20:41 | Noock

# re: C++的流设计很糟糕  回复  更多评论   

@Noock
我文章的目的,是要说明C++的这种机制存在缺陷,已经强调了很多次.你可以"规避"这个问题,不能否认我的结论.
2010-07-09 20:50 | 那谁

# re: C++的流设计很糟糕[未登录]  回复  更多评论   

没太看明白。lz的意思是不是说C++的流设计倒是用户不能确定什么时候写入的信息会真正流入到IO设备里,如果是这样的话我觉得这种东西应该放在文档里。

stream << "abc" << "def" << endl; 这里你看不出来什么时候结束,
难道printf("%s%s\r\n", "abc", "def");你就能看出来了? 还不是要看文档才知道?
2010-07-09 23:15 | 欲三更

# re: C++的流设计很糟糕  回复  更多评论   

服务器写log的,通常量不会太大,每次构造一个对象也不会成为瓶颈吧;

我是这样解决的,既可以保证类型安全,也可以保证线程安全;
当然如果你不用syslog自己写一个线程安全的flush函数也没问题啊

struct Log
{
int _lv;
std::stringstream _ss;

~Log()
{
syslog("%s\n", ss.str().data());
}

template<typename T1, typename T2, ... typename Tn>
Log(int lv, const T1 &t1, const T2 &t2, ... typename Tn)
: _lv(lv)
{
_ss << t1 << t2 << ... tn;
}
}

int main()
{
Log(info, "a=", 1, "; b=", 2);
}
2010-07-10 16:55 | 路过

# re: C++的流设计很糟糕  回复  更多评论   

#define LOG(x) do{_log<<x<<endl;}while(0)

LOG(a<<b<<c<<d<<e);

这个呢,没有了<<endl的问题,编译器也能检查类型。
2010-07-12 20:30 | 陈梓瀚(vczh)

# re: C++的流设计很糟糕  回复  更多评论   

虽然我不是很懂C++,但是我还是懂逻辑的。
后面几个回复已经答复了你的问题。
C++的机制是最自然合理的处理方式。
其实出现问题的原因是:实例里面的logstream并没有按照C++ io的套路来,
没有加上一个结束的标示: endl.
2010-07-28 22:20 | halida

# re: C++的流设计很糟糕  回复  更多评论   

感觉楼主对C++语言还缺乏较为深入的理解,下面对几个问题做点说明,其实很简单,很多人不懂,是因为C++标准教材没这些东西。C++是一门在工业实践中成长起来的语言,工业界发明这些东西是因为需要,学院派却总跟不上进度,教材几十年一变。要用C++,就要做好准备,否则,你干嘛不用Java或者C#。

1. 关于所谓“频繁的构造/析构开销大”
你首先要清楚“构造”和“析构”中编译器到底为你做了什么。1.)分配对象空间:如果是在堆中分配对象,那么会有一个代价很大的堆分配(new,在2.7G的CPU上单线程new性能是5M次/秒);如果在堆栈上分配,内存分配代价几乎为零。2)调用构造函数和析构函数,这有两个开销,一个是调用本身的开销,一个是函数体内部代码的开销,很明显,前者才C++带来的额外开销。我可以告诉你的是,如果是内联,这个开销为0,如果不是内联,这个开销在2.7G的CPU上单线程性能是1200M次/秒,作为类比,2.7G的CPU上单线程可以做400M次32位整型变量写入操作,也就是这个开销比写一个整型变量还小。
现在,看看你说的情况,局部对象的构造和析构,每次的代价比写一个32位整型的变量还小得多,相比每次日志输出至少十几个字节的内存拷贝,这点开销完全可以忽略不计,除非打算每秒中打算做1M次的日志,它带来的代价不占用1%的CPU而已,不过事实是,每秒钟写不了1M次的文件IO。
最后从设计的角度考虑这个问题,你的系统打算每秒中写多少次日志,应该心理有数吧,从这个意义上,从设计的角度,上面我写的那些分析毫无必要,只是为了加深对C++的理解,事实是,即便“频繁的构造/析构开销大”很大,它们仍然不是系统的真正瓶颈,没必要过早优化。如果它们真成了瓶颈,你应该做的事情是,调整成合理的日志策略。





2010-08-03 22:20 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

2.所谓“比如log << "hello " << "world",是无法判断到底在输出"hello"还是"world"的时候上面的参数输入已经结束了”

其实,这个问题,流的设计者早已考虑到了,std::endl就是用来干这件事情的。事实上,自定义的流操控符,还可以干很多事情比如:
std::cout << v1 << mylock(v2) << v2 << myunlock(v2);
上面的mylock,myunlock就是自定义的操作符,用来给v2加锁解锁,而不输出任何字符。它到底能做什么,取决于你的想象力。我总爱把C++比作机械行业的钳工,他们比不上机器的速度,但没他们不行,很多事情机器做不了。使用正确的工具做正确的事情,如果你感觉不对,先想想选对工具没,而不是抱怨工具很烂。

额外,说明一点,有人告诉你sprintf存在写错的可能性,所以,你可以说,如果别人忘了写上他的endl怎么办?

我来告诉你吧,写错了其实没什么大不了的,问题关键是,写错了会带来什么危害。sprintf写错了,可能带来的是内存溢出覆盖,这才是我们恐惧他的原因,一个内存溢出带来的危害我就不说了。
反之,少写了一个endl,最多就是两行日志重叠,或者一个日志输出时间晚了一会儿。如果你真看到这个情况,把endl加上去就行了。

不知道现在是否能理解了,不要害怕bug,不要害怕写错,要怕会让你掉进深渊的bug。我得承认,这是C/C++的弱点,java/C#相对好很多。
C++最害怕的,就是指针操作,内存覆盖可以毁掉整个程序的运行基础,却不容易找到错误的代码。但这也是C++的优点,C++为什么要用流替换C的sprintf,就是要减少内存覆盖错误的机会。当然,C++中仍然有这种错误的机会,因为抛弃了指针,C++和Java就没区别了。如果说C是做操作系统的,java是做应用的,C++就是做系统和应用结合部的,只有理解了这点,你才能用好C++,而不是抱怨,它既没C简单,也没java安全。
事实是,C++就是这么个怪胎,比Java更快,比C更安全更有开发效率。



















2010-08-03 22:38 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

3. 关于“要使用这门语言写出正确的程序来,需要了解底下多少的细节呢?!”

首先答案是,不需要知道细节,只需要知道“规范”。C++真正的问题不是太复杂,而是在实践中缺乏规范,尤其在中国的软件作坊里面。就像你会开汽车一样的,你没比要知道汽车发动机原理,同样能把汽车开好。因为你遵守了开汽车的规范,比如启动的时候,慢加油门。

很多人的问题在于,在思想上,忽视了规范,到头来却怪东西太复杂。

其次是了解细节,可以工作更深入。再说了,就算复杂,C++能有多复杂,一个C++语言里面能有多少东西呢?相比一个Java库,这点东西真算不了什么。很多人掌握不好,是因为没有正正经经的机会去学,去练。这点像数学,学的时候比较枯燥,不管怎么说,这点东西就叫复杂,那只能说,做的应用系统太简单。
2010-08-03 22:50 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

4. 关于“假如需要考虑多线程的话,那么一次输入有多个函数函数中被调用”

要在多线程进行IO操作,肯定是要用锁的,就算你不直接用,系统API的流API,比如Win32的WriteFile,也是要用的。


所以,答案很简单,用锁。问题不在于有几次函数调用,而在于能否让这几次函数调用位于同一个锁当中。

传统上,一个sprinf,你可以加一次锁,就够了。
而现在呢,分成了好几次调用,那么就在这几次调用之间和之后加锁就行了,在本例中,也就是那个被认为过于调用繁琐的临时对象了,在它的构造函数加锁,在它的析构函数中解锁,就能保证输出的原子性。如果这样还不满意,还可以考虑流操控符加锁,不过有点危险。


不过呢,说道最后,如果你明白,那个看似效率低下的临时对象其实对整行的输出做了缓存,所以在glog中,临时对象中是没必要用锁的,因为临时对象中保存的字串是不会被多线程打断的,它能够保证所有的“<<”调用在输出上的原子性。最后析构函数中,真正进行输出时,在下层的实际输出位置,实际上是有锁。



2010-08-04 00:03 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

5. 最后谈一下,C++流的真正缺点?

从安全性的角度讲,C++流相对sprintf是一次飞跃。从实际项目来看,C++程序员的代码产出和维护量,通常会数倍甚至几十倍于C程序员,这表面了在某些问题域上,C++比更有开发效率。

但由此带来的问题是,在代码量少的时候,C程序员可以花时间慢慢检查代码,保证sprintf没问题。而C++程序员再这样做效率就太低了。所以才会有了C++流的方案,C++流设计者正是从实践中品尝到了sprintf的苦果。

事实是,C++语法形式,从实用性角度,的确很蹩脚。而且性能只有sprintf的1/3.不过实际环境下,性能通常不是问题,流输出很少会是一个应用系统真正的瓶颈。

蹩脚的语法,是个问题,尤其当你需要做格式控制的时候,代码可能非常长。这个问题,我的看法是,写的时候可能多花点时间,不过以后维护起来就轻松了。毕竟,我宁愿选择安全性,花三天时间去找一个缓冲区溢出是不会宁人愉悦的。当你认为语法问题很重要时,通常暗示代码管理上有问题。我通常认为代码的书写只占20%的时间,80%时间是在维护代码。维护效率远比书写效率重要。

在C++领域,新发明似乎是没有止境的,有一个新的,利用重载“()”操作符的格式化库出现了,具体我本人没有用过,看起来还不错,据说在性能上优于sprintf,在安全性上不输于C++流,在格式上类似sprintf。由于缺乏大规模应用,实际情况如何,还不好说。


就我本人而言,我认为C++流的效率和格式问题,并非致命问题,所以也就不急着使用更先进的东西了,短期内我C++流仍是最好的格式化输出工具。除非,项目主要业务逻辑就是格式化字符串,那也许我会选择sprintf或者其他的东西。















2010-08-04 00:22 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

最后,我感觉楼主,似乎想在一个输出语句中,输出很长很长的,可能跨越多次物理输出的内容。

这样做,首先代码不易理解,不易修改维护。

根据本人的实际经验来看,日志输出最好还是按实际物理行为单位比较好,所以glog没有支持所谓endl特性。

楼主可能真正担心的是另一个问题,在多线程程环境下,想要连续输出的几行文本,会被其他线程打断,以致阅读性变差。

对此,我建议,如果不希望被打断,使用glog那就需要八几行输出写在一个glog句子,作为一次原子输出就行了。但是,如果楼主对这样的原子输出,还要求再被分成多次物理输出,那这是为什么呢?有这个必要吗?既然打算连续输出几行,且在一个语句之中,整个语句时间是非常快的,对观察者而言,一次原子输出是由一次物理输出还是多次物理输出构成,没有任何实际意义。








2010-08-04 00:36 | maxime

# re: C++的流设计很糟糕  回复  更多评论   

刚才服务器崩溃了,估计又是(我是用)printf的(问题)。譬如%s,结果我的参数为int型别。
之前测试程序时,就出过这类错误。不过解决倒也容易。
2010-08-26 21:29 | 哭啊

# re: C++的流设计很糟糕  回复  更多评论   

都是大神
2010-10-29 14:55 | 刀刀

# re: C++的流设计很糟糕  回复  更多评论   

在CPP 博客批CPP
哈哈 找抽

懂c++的c太他们简单的玩意
2010-12-27 14:11 | 法国风格

# re: C++的流设计很糟糕  回复  更多评论   

Set your life time more simple take the <a href="http://bestfinance-blog.com">loans</a> and everything you need.
2011-09-02 12:52 | CecileOneal25

# re: C++的流设计很糟糕  回复  更多评论   

很不错。
2011-09-08 23:25 | tall ugg outlet

# re: C++的流设计很糟糕  回复  更多评论   

你们都弱爆了,想想这样的log api是怎么实现的

jj_log(0, (%p, fd)(%d, backlog)((eh_format), er));

jj_log(4, "ERROR: %d", er);
2011-11-01 09:42 | tankxx

# re: C++的流设计很糟糕  回复  更多评论   

超级牛掰的一群人啊。。。佩服
2011-11-23 13:07 | 烟圈

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理