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

发布于 2024-08-21 08:11:17 字数 1067 浏览 11 评论 0原文

考虑以下最小示例:

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

然而,这会导致各种含糊不清。

Consider the following minimal example:

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

The output, both on g++ and on Visual C++, is

hello world
hello world
0x4012a4

The version that writes to a temporary object, myostream(cout), appears to prefer the member operator ostream::operator<<(void *), instead of the free operator operator<<(ostream &, char *). It seems to make a difference whether or not the object has a name.

Why does this happen? And how do I prevent this behaviour?

Edit: Why it happens is now clear from various answers. As to how to prevent this, the following seems appealing:

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

However, this results in all kinds of ambiguities.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

私野 2024-08-28 08:11:17

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

operator<<(ostream &, char *)

If an object doesn't have a name (i.e. it is a temporary), it cannot be bound to a non-const reference. Specifically, it can't be bound to the first parameter of:

operator<<(ostream &, char *)
寻梦旅人 2024-08-28 08:11:17

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

如果需要,您可以添加一个调用,例如

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

将右值转换为引用。

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

rvalues can't be bound to non-const reference. So in your example the temporary of type ostream can't be the first argument of free operator<<(std::ostream&, char const*) and what is used is the member operator<<(void*).

If you need it, you can add a call such as

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

which will transform the rvalue into a reference.

Note that in C++0X, the introduction of rvalue reference will allow to provide overload of operator<< taking rvalue references as parameter, solving the root cause of the issue.

巾帼英雄 2024-08-28 08:11:17

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

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

I just realized part of the answer. The temporary is not an lvalue, so it cannot be used as an argument of type ostream &.

The question "how can I make this work" remains...

凑诗 2024-08-28 08:11:17

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

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

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

Since none of the answers so far seem to give a clean solution, I will settle for the dirty solution:

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

This is only possible because myostream has a copy constructor. (Internally, it is backed by a ref-counted std::stringbuf.)

本王不退位尔等都是臣 2024-08-28 08:11:17

虽然 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;
        }
};

While C++11 does resolve this issue since there are rvalue references, I think this might be a workaround for pre-C++11.

The solution is to have a member function << operator where we can cast to a non-const reference to the base class:

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;
        }
};
十秒萌定你 2024-08-28 08:11:17

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

临时存在于堆栈中,通常会传递给另一个函数或对其调用单个操作。因此,如果您对其调用自由运算符:

operator<<(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

Well, I don't know the C++ spec that causes this, but it is easy to suss out why it happens.

A temporary lives on the stack, usually to be passed to another function or to have a single operation called on it. So, if you call the free operator on it:

operator<<(myostream(cout))

It is destroyed at the end of this operation and the second "<<" operator to append the endl would reference an invalid object. The return value from the free "<<" operator would be a reference to a destructed temporary object. The C++ spec probably defines rules about free operators to prevent this scenario from frustrating and confusing C++ programmers.

Now, in the case of a "<<(void*)" member operator on the temporary, the return value is the object itself, which is still on the stack and not destroyed, so the compiler knows not to destruct it but to pass it to the next member operator, the one that takes the endl. Operator chaining on temporaries is a useful feature for succinct C++ code, so I'm sure the C++ spec designers considered it and implemented the compiler to support it intentionally.

edit

Some have said that it is to do with a non-const reference. This code compiles:

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

And it returns

  This works destructing illegal
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文