11

考虑以下最小示例:

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

然而,这会导致各种歧义。

4

6 回答 6

7

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

operator<<(ostream &, char *)
于 2010-02-11T16:16:00.833 回答
6

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

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

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

这会将右值转换为引用。

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

于 2010-02-11T16:25:04.337 回答
3

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

“我怎样才能使这项工作”的问题仍然存在......

于 2010-02-11T16:13:09.420 回答
1

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

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

这只是可能的,因为myostream有一个复制构造函数。(在内部,它由 ref-counted 支持std::stringbuf。)

于 2010-02-12T08:57:45.557 回答
0

虽然由于存在右值引用,C++11 确实解决了这个问题,但我认为这可能是 C++11 之前的一种解决方法。

解决方案是拥有一个成员函数 << 运算符,我们可以在其中强制转换为对基类的非常量引用:

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;
        }
};
于 2015-09-16T14:18:59.990 回答
-1

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

临时存在于堆栈中,通常用于传递给另一个函数或对其调用单个操作。因此,如果您致电免费运营商:

运算符<<(myostream(cout))

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

现在,在临时的“<<(void*)”成员运算符的情况下,返回值是对象本身,它仍然在堆栈上并且没有被破坏,所以编译器知道不破坏它而是传递它传递给下一个成员运算符,即接受 endl 的成员运算符。临时操作符链对于简洁的 C++ 代码来说是一个有用的特性,所以我确信 C++ 规范设计者考虑过它并有意实现了编译器来支持它。

编辑

有人说这与非常量引用有关。此代码编译:

#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
于 2010-02-11T16:20:00.447 回答