问题 涉及临时工的运算符重载决策的顺序


请考虑以下最小示例:

#include <iostream>

using namespace std;

class myostream : public ostream {
    public:
        myostream(ostream const &other) :
            ostream(other.rdbuf())
        { }
};

int main() {
    cout << "hello world" << endl;

    myostream s(cout);
    s << "hello world" << endl;

    myostream(cout) << "hello world" << endl;
}

g ++和Visual C ++上的输出都是

hello world
hello world
0x4012a4

写入临时对象的版本, myostream(cout),似乎更喜欢会员运营商 ostream::operator<<(void *)而不是自由运算符 operator<<(ostream &, char *)。无论对象是否具有名称,似乎都有所不同。

为什么会这样?我该如何防止这种行为?

编辑:为什么它发生现在从各种答案清楚。至于如何防止这种情况,以下似乎很吸引人:

class myostream : public ostream {
    public:
        // ...
        myostream &operator<<(char const *str) {
            std::operator<<(*this, str);
            return *this;
        }
};

然而,这导致各种模糊。


5828
2018-02-11 16:09


起源

你可以将这个问题的答案视为另一个问题的答案,作为至少与你想要达到的目标类似的东西的起点: stackoverflow.com/questions/469696/... 您必须向类添加功能以接受输入修饰符(std :: hex,std :: endl ...),但这不应该太难。 - David Rodríguez - dribeas


答案:


rvalues不能绑定到非const引用。所以在你的例子中,临时类型ostream不能是自由运算符<<(std :: ostream&,char const *)的第一个参数,使用的是成员运算符<<(void *)。

如果需要,可以添加如下调用

myostream(cout).flush() << "foo";

这会将rvalue转换为参考。

请注意,在C ++ 0X中,引入rvalue引用将允许提供操作符<<的重载,将rvalue引用作为参数,解决问题的根本原因。


6
2018-02-11 16:25



你的意思是“rvalues不能被束缚......”?并且“......将右值变成左值......”? - Éric Malenfant
是的,我已经修正了我的回复。谢谢。 - AProgrammer


如果对象没有名称(即它是临时的),则不能绑定到非const引用。具体来说,它不能绑定到第一个参数:

operator<<(ostream &, char *)

7
2018-02-11 16:16



..直到C ++ 0x出现,并赋予我们r值引用。 - dirkgently


我才发现 部分 的答案。临时不是左值,因此不能用作类型的参数 ostream &

“我怎样才能做到这一点”的问题仍然存在......


3
2018-02-11 16:13



最简单的:提供使您工作的成员函数。 - David Rodríguez - dribeas


由于到目前为止没有一个答案似乎给出了一个干净的解决方案,我将解决这个肮脏的解决方案:

myostream operator<<(myostream stream, char const *str) {
    std::operator<<(stream, str);
    return stream;
}

这是唯一可能的原因 myostream 有一个复制构造函数。 (在内部,它由重新计数支持 std::stringbuf。)


1
2018-02-12 08:57





虽然C ++ 11确实解决了这个问题,因为有rvalue引用,我认为这可能是pre-C ++ 11的一种解决方法。

解决方案是有一个成员函数<<运算符,我们可以将其转换为对基类的非const引用:

class myostream : public ostream {
    public:
        // ...
        template<typename T>
        ostream &operator<<(const T &t) {
            //now the first operand is no longer a temporary,
            //so the non-member operators will overload correctly
            return static_cast<ostream &>(*this) << t;
        }
};

0
2017-09-16 14:18





好吧,我不知道导致这种情况的C ++规范,但很容易理解它为什么会发生。

临时存在于堆栈上,通常传递给另一个函数或者在其上调用单个操作。所以,如果你打电话给它的免费运营商:

运营商<<(myostream(COUT))

它在此操作结束时被销毁,并且附加endl的第二个“<<”运算符将引用无效对象。来自自由“<<”运算符的返回值将是对被破坏的临时对象的引用。 C ++规范可能定义了关于自由运算符的规则,以防止这种情况使C ++程序员感到沮丧和困惑。

现在,在临时的“<<(void *)”成员运算符的情况下,返回值是对象本身,它仍然在堆栈上而不被销毁,因此编译器知道不要破坏它但是要通过它到下一个成员运算符,即获取endl的运算符。运算符链接临时代码对于简洁的C ++代码来说是一个很有用的特性,所以我确信C ++规范设计者会考虑它并实现编译器以有意支持它。

编辑

有些人说它与非const引用有关。此代码编译:

#include <iostream>
using namespace std;
class myostream : public ostream { 
    public: 
        myostream(ostream const &other) : 
            ostream(other.rdbuf()) 
        { } 
            ~myostream() { cout << " destructing "; }
    }; 
int _tmain(int argc, _TCHAR* argv[])
{
    basic_ostream<char>& result = std::operator << (myostream(cout), "This works");
    std::operator << (result, "illegal");
         return 0;
}

它回来了

  This works destructing illegal

-1
2018-02-11 16:20



对不起 - 你对临时生命的理解是错误的。
请随时赐教。 AFAIK,这里的描述与我的解释一致: publib.boulder.ibm.com/infocenter/comphelp/v8v101/...。如果对临时使用静态方法,则必须在清除该操作的调用堆栈时销毁临时方法。如果它被用作成员操作的“this”,那么它可以在操作完成后继续存在,因为它位于调用堆栈的底部,因此链式成员操作符将起作用但静态方法不会。 - David Gladfelter
您发布的链接是一个众所周知的C ++信息来源(并表现出奇怪的行为,将我翻到另一个页面)。 C ++标准是这里的引用,它(有效地)说临时必须在它所属的完整表达式结束之前一直存在,在本例中是<<运算符的完整链。在我的回答中给出了代码不能按预期工作的真正原因。
这是我承认不会知道的事情之一,但对我而言 a << b << c; 是语法糖 operator<<( operator<<( a, b ), c );,所以临时是作为整个表达式的一部分创建的,并且作为你链接到的文章中的第二句,应该在 ;, 我对吗? - David Rodríguez - dribeas
它与非const引用无关,它与临时引用有关。一个命名的非const引用可以绑定到一个非const引用参数,一个未命名的临时引用参数不能。