MSVC 与 GCC / Clang 的流、内部字符串和操作排序问题
大家好,
我最近刚刚开始更认真地使用 MSVC 来完成我的一个跨平台项目,同时通过以下方式测试输出chainedSTD 流(IE。一系列的obj.foo() << endl << obj.bar() << endl << [..etc]
)我在使用内部更新的字符串时遇到了一种行为,既没有预料到,也没有在 Linux 上遇到过GCC or Clang.
编译器版本为 GCC 7.5、Clang 11.0 和 MSVC 14.0,均启用了 c++17 标准(尽管尚未完成)。 [edit:使用 MSVC 16.6.3 时出现同样的问题(编译器内部版本 19.26.28806.0) ]
为了快速理解这里的问题的简化版本:
#include <iostream>
#include <ostream>
#include <string>
class Sample {
std::string s;
int x;
public:
Sample() = default;
friend std::ostream& operator<<(std::ostream& os, const Sample& a);
// Update internal value, return the object.
Sample const& set(std::string ss, int xx) {
s = ss;
x = xx;
return *this;
}
// Update internal value, return the string.
std::string const& setStr(std::string ss, int xx) {
set(ss, xx);
return s;
}
// Update internal value, return the int.
int const& setX(std::string ss, int xx) {
set(ss, xx);
return x;
}
};
// Output the object integer, same behavior with the string
// or if we flush inside or anything.
std::ostream& operator<<(std::ostream& os, Sample const& a)
{
os << a.x;
return os;
}
int main() {
Sample a;
// GCC / Clang | MSVC
std::cerr << a.set("0", 0) << std::endl // 0 0
<< a.set("1", 1) << std::endl // 1 0
<< a.set("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setStr("0", 0) << std::endl // 0 0
<< a.setStr("1", 1) << std::endl // 1 0
<< a.setStr("2", 2) << std::endl; // 2 0
std::cerr << "content : " << a << std::endl; // 2 0
a.set("",-1); std::cerr << std::endl;
std::cerr << a.setX("0", 0) << std::endl // 0 0
<< a.setX("1", 1) << std::endl // 1 1
<< a.setX("2", 2) << std::endl; // 2 2
std::cerr << "content : " << a << std::endl; // 2 2
}
看来,对于字符串或流式版本,所有操作都使用相同的最终变异字符串对象,但我不明白为什么会这样(再次,在 GNU / Linux 工具链上没有问题).
我可能会补充一点,如果我们解开流,这个排序问题就会消失:
std::cerr << a.set("0", 0) << std::endl; // "0"
std::cerr << a.set("1", 1) << std::endl; // "1"
std::cerr << a.set("2", 2) << std::endl; // "2"
我一开始以为是冲洗问题,但测试显示并非如此。实际使用endl
甚至flush
在每个链式调用之间不执行任何操作。
它可能是 Visual-C++ 甚至CPP101已知的基本行为(关于记忆之类的),但我对此一无所知,所以我将非常感谢您提供的任何建议,因为它在我的书中非常奇怪。
Thanks !
Edit
具有讽刺意味的是,我已经能够在 GNU / Linux 上重现该问题(使用我的项目,而不是上面的代码),试图通过模板变量扩展找到替代方案,但这里的事情是:
void println() // base function
{
std::cerr << std::endl;
}
template<typename T, typename... Ts>
constexpr void println(T head, Ts... tail)
{
std::cerr << head << std::endl;
println(tail...);
}
int main()
{
int i;
i = 0;
println(++i, ++i, ++i); // 3 3 3
i = 0;
println(i++, i++, i++); // 2 1 0
}
在 MSVC 上,流似乎像这种后增量可变参数模板一样工作:结果在某种程度上是向后的(或者更像是后递归应用)。我不确定这对我来说是否有意义。