当
std::istringstream
/std::ostringstream
上的 I/O 操作失败时,我想短路以避免不必要地调用已经 failed
(x)stringstream上的其余
<<
/>>
运算符目的。我的一些重载的 <<
/>>
运算符会执行一些重要的格式化/解析操作,因此在失败的流对象上调用它们会浪费它们的 CPU 周期。
生成的字符串流(下面代码中的
oss
)然后被整块插入到std::ofstream
中。我使用 std::cout
代替进行演示。
我想到了两种方法。下面是一个非常小的例子。
#include <bitset>
#include <sstream>
#include <iostream>
#include <cstdlib>
int main()
{
constexpr auto delimiter { ';' };
std::ostringstream oss;
const bool success = oss << "Hi" &&
oss << delimiter &&
oss << 8.02 &&
oss << delimiter &&
oss << false &&
oss << delimiter &&
oss << std::bitset<10> { 0b01010 } &&
oss << delimiter &&
oss << '\n';
if ( success ) std::cout << oss.view(); // Hi;8.02;0;0000001010;
else return EXIT_FAILURE;
}
int main()
{
constexpr auto delimiter { ';' };
std::ostringstream oss;
oss.exceptions( std::ios::failbit | std::ios::badbit );
try
{
oss << "Hi"
<< delimiter
<< 8.02
<< delimiter
<< false
<< delimiter
<< std::bitset<10> { 0b01010 }
<< delimiter
<< '\n';
}
catch ( const std::ios_base::failure& e )
{
return EXIT_FAILURE;
}
std::cout << oss.view(); // Hi;8.02;0;0000001010;
}
显然,编译器为这两种方法生成不同的代码(基于异常的方法的代码明显更少),尽管我不确定在上述用例中哪一种方法可以更快。
哪一个可能是更好的解决方案?我必须向流对象插入/提取大约 20 个值。如果做出正确的选择,可能会提高效率。
由于“什么更快”这个问题无法回答,我将尝试解释每种情况下发生的情况。尽管如此,我还是想强烈强调,这个答案应该被视为学术上的考虑,你不应该仅仅根据你在这里读到的内容来引入代码更改。分析您的代码,找到瓶颈,修复它们,当对可能的修复有疑问时,对它们进行基准测试(或同时实现并再次分析)。永远不要基于预感某些事情可能会很慢而进行优化。
你必须回答的问题是“我想阻止什么?”。如果您试图阻止在流处于失败状态时调用内置
operator<<
的主体,那么正如 Pete 在评论中指出的那样,这已经为您完成了。这意味着,不会尝试将数值转换为字符串表示形式,也不会将任何字符串复制到流中,也不会发生对输出或文件的任何操作。如果您对自己的类型进行重载operator<<
,并且这些重载在将结果传递给内置运算符之前做了重要的工作,那么该工作仍然会发生)。
示例:
std::ostream& operator<<(std::ostream& os, const MyType1& t) {
return os << t.a << ' ' << t.b << ", " << t.c; // almost nothing is done if !os
}
std::ostream& operator<<(std::ostream& os, const MyType2& t) {
return os << t.toString(); // `toString` has to be evaluated fully before os is checked for fail
}
但是,如果您想阻止对操作数求值到
operator<<
(例如,构造临时对象),那就更成问题了。您的第一种方法确实可以防止在 std::bitset<10>
进入失败状态后构造任何类似 oss
的对象。这是以在汇编中添加多次跳转为代价的,而且跳转也不利于性能。由于新的评估规则顺序将排序添加到operator <<
,您的第二种方法只能保证在 C++17 及更高版本中执行相同的操作。在 C++17 之前,未指定是否创建所有剩余对象。
所以,总结一下:
toString()
。代价是在汇编代码中添加更多分支,这通常不是最好的性能(编译器可能会也可能不会优化以假设流不太可能失败)。oss
执行任何操作之前评估所有操作数。为了在示例中展示它,我们假设此输出以某种方式失败:
const bool success = oss << "Hi" &&
oss << delimiter && // this one failed
oss << std::bitset<10> { 0b01010 } &&
oss << delimiter &&
oss << std::string(13, 'n');
oss << '\n';
方法 1 不会创建
std::bitset
或 str::string
对象,方法 2 将创建 std::bitset
并在 C++17 中立即抛出(避免创建 std::string
),在 C++17 之前,方法 2 取决于编译器 - 将创建 std::bitset
,std::string
可能会也可能不会。