5

我制作了一个自定义流类型,称为它error_stream,它派生自std::ostringstream. 我还为名为throw_cpp_class(throw_cpp的实例throw_cpp_class) 的流制作了一个自定义操纵器。我的目标是拥有这种语法:

error_stream s;
s << "some error " << message() << throw_cpp; // throw_cpp throws an exception containing contents of the stream.

我发现,通过定义一个插入操作符,它将对流的右值引用作为第一个操作数,我现在可以这样做:

error_stream() << "some error " << message() << throw_cpp;

插入运算符如下所示:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&)
{
    throw s.str();
    return s;
}

这里发生了什么?为什么我可以返回需要error_stream&&an的类型的值error_stream&?(这会调用移动构造函数吗?)。这是非常低效的吗?(考虑到例外情况应该很少见,我并不是真的在乎)。

4

2 回答 2

15

使用此代码:

error_stream& operator<<(error_stream&& s, const throw_cpp_class&)
{
    throw s.str();
    return s;
}

您可以error_stream&& s作为 a返回error_stream&,因为它s左值,而不是右值。

“什么?” 你问?“但我看到&&了!”。C++ 的这一部分很棘手。当您看到type&& s(并且type不是模板)时,这意味着该变量是一个右值引用,它是一个从右值“构造”的引用。但它有一个名字:s. 任何有名字的东西都是左值。这就是为什么您有时必须调用的原因std::move,因为您必须让编译器知道您希望它再次将该变量视为右值。

这会调用移动构造函数吗?)。

不,它只是返回对 lvalue 的引用s

这是非常低效的吗?(考虑到例外情况应该很少见,我并不是真的在乎)。

不,因为没有复制,甚至没有发生移动。


与您的实际问题无关,流的大多数重载是:

ostream& operator<<(ostream&& s, const T&)

那么这意味着除非throw_cpp第一件事 streamed,否则不会调用您的重载,因为之前的事情 streamed 将返回 an ostream&,而不是 an error_stream&&。(注意它们应该是模板,但很多不是,这与重点无关)您必须将其转换回error_stream.

此外,这不是机械手的工作方式。操纵器是函数,当您将这些函数流式传输到流时,流会调用该函数并将自身作为参数传递,因此您需要更像这样的东西:

template <class exception, class charT, class traits>
std::basic_ostream<charT,traits>& throw_cpp(std::basic_ostream<charT,traits>& os)
{
    error_stream& self = dynamic_cast<error_stream&>(os); //maybe throws std::bad_cast
    throw exception(self.str());
    return os; //redundant, but the compiler might not know that.
}

它在这里工作(使用字符串流)

于 2013-06-25T01:20:29.630 回答
3

T&&是一个右值引用,但它的值类别是一个引用(一个左值),所以它可以存储在一个左值引用中。在这种情况下也不会调用移动/复制构造函数,因为它是通过引用获取/返回的。

我不会说这至少是低效的,而是右值引用的常见和惯用用法。

于 2013-06-25T01:19:40.163 回答