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

时间:2010-02-11 16:09:33

标签: c++ operator-overloading member-functions overload-resolution

考虑以下最小例子:

#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;
        }
};

但是,这会导致各种歧义。

6 个答案:

答案 0 :(得分:7)

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

operator<<(ostream &, char *)

答案 1 :(得分:6)

rvalues不能绑定到非const引用。因此,在您的示例中,类型ostream的临时类型不能是自由运算符&lt;&lt;(std :: ostream&amp;,char const *)的第一个参数,而使用的是成员运算符&lt;&lt;(void *)。< / p>

如果需要,可以添加

等电话
myostream(cout).flush() << "foo";

将rvalue转换为引用。

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:1)

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

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

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

答案 4 :(得分:0)

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

解决方案是具有成员函数&lt;&lt;我们可以转换为基类的非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;
        }
};

答案 5 :(得分:-1)

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

临时存在于堆栈上,通常要传递给另一个函数或者在其上调用一个操作。所以,如果你打电话给它的自由运算符:

运营商LT;≤(myostream(COUT))

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

现在,在临时的“&lt;&lt;(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