在我们的生产代码中,我们不能使用Boost或C ++ 0x。在这种情况下,使用sprintfstringstream格式化字符串很烦人,这促使我编写了自己的小Formatter类。我很好奇这个类的实现或它的使用是否引入了任何未定义的行为。

尤其是此行是否已完全定义:

Reject( Formatter() << "Error Recieved" << 42 << " " << some_code << " '" << some_msg << "'");


我相信可以,但是我希望得到同行评审。

关注的三个主要问题:


在单个序列点内分配?是UB吗?
我对临时期限有疑问吗?
我的Formatter类(或它的预期用途)是否引入了任何UB?

Formatter类同时具有( operator<<operator std::string。目的是将Formatter()类用作使用std::string的任何函数的const std::string&参数的临时类。

这是类的定义:

class Formatter
{
public:
 Formatter() {};
 template<class Field> Formatter& operator<<(Field f)
 {
  ss_ << f;
  return *this;
 }
 operator std::string() const { return ss_.str(); }
private:
 std::stringstream ss_;
};


这是一个完整的测试工具,包括上面的定义。您应该能够按原样编译和运行。您看到UB了吗?

#include <cstdlib>
#include <string>
#include <sstream>
#include <iostream>

class Formatter
{
public:
 Formatter() {};
 template<class Field> Formatter& operator<<(Field f)
 {
  ss_ << f;
  return *this;
 }
 operator std::string() const { return ss_.str(); }
private:
 std::stringstream ss_;
};

void Reject(const std::string& msg)
{
 std::cout << "Recieved Message: '" << msg << "'" << std::endl;
}

int main()
{
 const char& some_code = 'A';
 const char* some_msg = "Something";

 Reject( Formatter() << "Error Recieved" << 42 << " " << some_code << " '" << some_msg << "'");
}


#1 楼

除了已经说过的内容之外,我还将:


将字符串流标记为公共。这不会影响您的代码的大多数使用,并且已经可以使用自定义操纵器进行破解以获取“内部”流对象,但是它将使那些需要访问内部流的对象(例如避免使用字符串)成为可能。复制stringstream接口中固有的内容),并知道允许他们想要的实现的细节。当然,0x move语义可以缓解这种需求,但是在返回字符串之前,请先检查流。如果它处于失败状态,则引发异常(或至少在返回字符串之前将条件记录在某处)。对于大多数用途来说,这种情况不太可能发生,但是,如果确实发生这种情况,您会很高兴地发现流失败了,而不是搞砸了格式化,同时又想知道为什么“它根本不起作用”。 />关于双重分配,根本没有分配。顺序点应该基本上是人们期望的,但是,确切地说,它看起来像: br />

Formatter()和expr_a都在标记为1的插入之前发生。
上面加上插入1,加上expr_b在插入2之前发生。插入2,加上expr_c发生在插入3之前。在调用some_function之前发生。

要添加有关临时对象的讨论,所有创建的临时对象都在表达式中:

some_function(((Formatter() << expr_a) << expr_b) << expr_c);
//                          1          2          3


直到包含some_function调用的完整表达式的末尾,它们才会被销毁,这意味着不仅字符串将被传递给some_function,而且some_function届时将已经返回。 (否则将抛出异常,并且在展开时它们将被销毁,等等。)

为了处理所有操纵器,例如std :: endl,请添加:

some_function(Formatter() << make_a_temp() << "etc.")
//            one temp       another temp     and so on


我已经多次使用这种模式来包装流,并且它非常方便,因为您可以内联使用它(如您所做的那样),或者创建Formatter变量来进行更复杂的操作(考虑循环)根据条件插入流中等)。尽管仅当包装器执行的功能比您在此处执行的操作要多时,后一种情况才重要。 :)

评论


\ $ \ begingroup \ $
危险是expr_c可能与expr_a同时发生。
\ $ \ endgroup \ $
–重复数据删除器
2015年10月20日,11:49

#2 楼

只需三个注释:


我将删除空的构造函数。
如何处理std :: manipulators?
您是否不想通过const传递字段值



 template<class Field> Formatter& operator<<(Field const& f)
                                                // ^^^^^^


您的关注点:




是否有在单个序列点内进行双重分配?是UB吗?



没看见。
看起来不错。



我有问题吗?




没有不要这样。

评论


\ $ \ begingroup \ $
机械手有什么问题?您仍然可以为其使用模板化插入运算符。 Formatter()<< std :: hex;。你是说像Formatter()。width(10)之类的东西吗?
\ $ \ endgroup \ $
–威廉姆特
2011-1-27的3:43

\ $ \ begingroup \ $
@wilhelmtell:实际上我在考虑std :: endl。但这可能是设计使然。尽管在Formatter对象的上下文中使用std :: endl可能没有多大意义,但仍可能使一些仅将旧代码移植的用户混淆,这些旧代码与std :: endl一起使用时将无法编译。
\ $ \ endgroup \ $
–马丁·约克
2011年1月27日5:24



\ $ \ begingroup \ $
机械手无法直接工作。您需要手动添加对它们的支持。看看弗雷德·努克(Fred Nurk)的答案
\ $ \ endgroup \ $
–DavidRodríguez-dribeas
2011年1月27日15:25

\ $ \ begingroup \ $
@DavidRodríguez-dribeas:这正是我提到它的原因。
\ $ \ endgroup \ $
–马丁·约克
2011年1月27日19:40

\ $ \ begingroup \ $
约克,我应该在此添加一些@wilhelmtell。评论不是关于您的答案,而是关于他的评论。对困惑感到抱歉 :)
\ $ \ endgroup \ $
–DavidRodríguez-dribeas
2011年2月2日,12:15

#3 楼

我认为您可能会担心临时的终身问题。 (我知道我已经基于类似的代码。)

作为Reject的参数创建的临时对象将有一个生命周期绑定到它所创建的表达式。(在C ++标准中)。因此,即使您的转换运算符返回值,它们也将在包含Reject的表达式之后被销毁。

评论


\ $ \ begingroup \ $
+1:我还担心单个序列点中的双重分配。
\ $ \ endgroup \ $
– John Dibling
2011-1-26 22:55

#4 楼


我将使用std::ostringstream,因为您没有使用(似乎需要)std::istringstream的格式化功能。
格式化失败时会发生什么?您可以检查失败吗?


#5 楼

看起来还可以空的ctor是不必要的;编译器生成一个就可以了。 “已接收”应拼写为“已接收” :)

#6 楼

看起来不错不必太担心,最终会过度设计一个简单的解决方案:)

编辑:

实际上,我会将operator<<的参数设置为const引用。 />

评论


\ $ \ begingroup \ $
我同意!我希望没有问题,因为Formatter确实是一个很小的小发明。
\ $ \ endgroup \ $
– John Dibling
2011年1月26日在22:17