学过 c 的人都知道,c 中函数参数的求值顺序是由编译器的实现决定的,c 标准并没有规定求值顺序。每当讲到这,下面这个经典的例子总会出现:
int i = 0;
printf("%d,%d", i++, i++);
我们没办法确定是打印1,0,还是打印0,1,这取决于哪个i++先执行。
我很少碰到参数求值顺序的问题,我以为我不会碰到,也就慢慢的忘记了这条警告,今天我以一种让我“恐惧”的方式碰到了。
send(sfd, mPacket.content(), mPacket.contentSize(), MSG_NOSIGNAL));
通常情况,mPacket.content() 是一个 const 函数,它不会改变mPacket的内容。后来,功能升级,mPacket.content() 改成了非 const 函数,祸根由此种下。升级后的mPacket,mPacket.contentSize() 在有限的情况下,依赖于 mPacket.content() 的调用。在 send 函数调用时,因为函数参数求值顺序不确定,所以 mPacket.content() 和 mPacket.contentSize() 的调用顺序也就不确定,如果先调用前者,一切正常,如果先调用后者,等前者调用时,mPacket.contentSize() 的值已经不是它传递给 send 函数的那个值了。虽然在这里“参数有求值顺序依赖,但程序行为没有影响”,但是,我不可能永远这么幸运。
我之所以感到“恐惧”,是因为我之前没有意识到,“类方法”本质上和 i++ 一样具有副作用,用在函数参数中时,要小心,而在我意识到之前,我写的代码是没有保障的(虽然它现在没出问题)。
诫言:不要在同一个函数调用中使用i++两次,不要在同一个函数调用中使用同一个类的两个方法,除非他们是const的,并且没有操作mutable的成员变量。标准库的 std::string::c_str() 和 string::size() 满足这两个条件。与其靠小心(使两个成员函数结果不依赖于调用顺序),不如靠接口(把它们声明为const)。首先使用的人会放心的同一个函数调用中使用,而不担心求值顺序(就像std::string),实现的人也省心,如果违反接口,编译器也会警告。