函数顺序调用

时间:2016-06-07 19:28:12

标签: c++

是否保证在以下代码中调用make_string函数之前构造GetLastError对象:

class make_string
{
public:
  template <typename T>
  make_string& operator<<(const T& arg)
  {
    _stream << arg;
    return *this;
  }

  operator std::string() const
  {
    return _stream.str();
  }

protected:
  std::ostringstream _stream;
};

// Usage
foo(make_string() << GetLastError());

3 个答案:

答案 0 :(得分:8)

不,这是不保证的。 make_string() << GetLastError()在语义上等同于函数调用operator<<( make_string(), GetLastError() ),并且未指定函数参数的评估顺序。

因此,编译器可以先创建make_string的实例,然后调用GetLastError(),然后调用所述make_string对象的成员函数,或者它可以先调用{{1}然后创建一个实例,然后调用成员函数。根据我的经验,第二个结果更有可能。

修改

在评论中也提出了一些有趣的问题,我认为值得解决。

声明是,由于GetLastError()是一个成员函数,整个语句在语义上与

相同
operator<<

这种说法确实是对的。但是,上述声明中没有排序!首先发生了什么 - make_string().operator<<(GetLastError()); 调用或GetLastError()构造函数未定义,因为此处缺少排序。

答案 1 :(得分:3)

未指定函数参数的评估顺序。

make_string() << GetLastError()是对operator<<函数的调用。

但是,为了保证在Windows API函数之后调用GetLastError,在创建错误消息时,您可以使用内置的||运算符,如下所示:

AnApiFunctionThatReturnsTrueOnSuccess()
    || fail( "Bah, it failed", GetLastError() );

此处保证评估顺序,因为内置||具有短路行为。仅当左手参数评估为false时,才会评估右侧参数。

然后fail函数看起来像

auto fail( std::string const& s, int const code = 0 )
    -> bool
{
    throw std::runtime_error( make_string() << s << " (code = " << code << ")" );
}

make_string()后保证的点上执行 GetLastError() ,以便对... API级别分配功能不会使GetLastError的结果无效。

答案 2 :(得分:1)

重载<<只是一个函数调用,而未指定的评估顺序。在你的情况下,make_string的ctor是空的(除了构造一个oss),所以没关系:在你的真实代码中,这可能不是真的。

解决这个问题的方法是:

foo(make_string() << [&]{return GetLastError();});

然后:

template<class T> struct tag{using type=T;};

并在对象中:

template<class F>
make_string& operator<<(const F& arg)
{
  output(arg, std::result_of<F const&()>{});
  return *this;
}
template<class F, class Test>
tag<typename Test::type> output(F const& f, Test const&) {
  _stream << f();
  return {};
}
template<class T, class...Unused>
void output(T const& t, Unused const&...) {
  _stream << t;
}

如果我们传递一个可调用的,我们调用它。

这是手动短路评估。

上面的一些代码可能包含拼写错误或类似错误:设计合理。假设result_of启用了SFINAE,正如C ++ 14所要求的那样。如果result_of失败,还有其他方法可以检测是否可以使用0参数调用某些内容。