我们可以重载运算符吗<<第一个参数的类型是 std::ostream&& 而不是 std::ostream&

Can we overload operator<< with the first parameter being of the type std::ostream&& instead of std::ostream&

提问人:user12002570 提问时间:3/18/2022 最后编辑:user12002570 更新时间:8/4/2022 访问量:180

问:

我了解到我们可以超载,如下所示:operator<<

class Person 
{
    public:
        friend std::ostream& operator<<(std::ostream& os, const Person& obj);
};

我完全理解引用参数类型的原因。例如,第一个参数是引用,因为无法复制流,第二个参数是引用,因为我们希望反映对原始对象所做的更改(如果有的话)。我知道,由于我们在第二个参数中有一个低级常量,因此无法更改其状态,并且通过使用引用,我们可以避免复制。operator>>

我的问题是,我们是否可以(并且应该)对第一个参数使用右值引用而不是左值引用,如下所示:

friend std::ostream& operator<<(std::ostream&& os, const Person& obj); //note the first parameter is rvalue-reference

我们有什么理由应该/不应该做上面显示的事情吗?更重要的是,如果我们这样做会发生什么。

同样,我的第二个问题是,我们是否可以将返回类型设置为 而不是 .在这种情况下,会发生什么/变化。std::ostream&&std::ostream&

PS:我正在学习C++,出于好奇问了这个问题。也就是说,加深我对引用和重载的了解。

C++ 函数 重载 IOSTREAM Ostream

评论

1赞 NathanOliver 3/18/2022
如果您使用右值引用而不是左值引用,则无法执行此操作cout << person;
1赞 PaulMcKenzie 3/18/2022
@Anya 我的问题是,我们是否可以(并且应该)对第一个参数使用右值引用而不是左值引用,如下所示: -- 您是否尝试编译此类代码?您应该需要几分钟才能查看您的提案是否真的被编译器接受。
1赞 PaulMcKenzie 3/18/2022
@Anya 不,我没有尝试编译——为什么不呢?如果一个提案根本不起作用,那又有什么用呢?这是你应该尝试的第一件事,然后问它是否可能。如果代码不编译,计算机不会爆炸。
3赞 PaulMcKenzie 3/18/2022
@Anya 不超过一分钟。我要求你做的是查看程序是否编译,而不是它是否运行。
2赞 BoP 3/18/2022
看看这个已经在标准 ostream.rvalue

答:

-1赞 Caleth 3/18/2022 #1

没有技术原因可以解释为什么你不能做其中任何一个。但是,如果这样做,则不会与定义运算符的所有其他内容进行互操作。ostream

如果输入是,则不能在左侧有左值流ostream &&

Person p;
std::cout << p // can't bind std::cout to ostream&&

将右值引用返回到通过左值引用接收的内容是不礼貌的。

std::ostream&& operator<<(std::ostream& os, const Person& obj) {
    // something involving obj
    return std::move(os); // stealing something that shouldn't be stolen!
}

如果你想有一个带有临时表达式的表达式,你可以。标准库定义了一个模板,该模板采用右值流,应用等效的左值流操作,并移动返回该流。ostream

评论

0赞 user12002570 3/18/2022
我想你在评论中打错了字:“// can't bind ostream&& to ostream&” should be “// can't bind ostream&& to oftream&”
0赞 Caleth 3/18/2022
@Anya 否,结果是 的 (临时)std::fstream("person.txt") << pstd::ostream &fstream
0赞 user12002570 3/18/2022
哦,好吧,由于派生到基础转换,对吧?
0赞 Jakob Stark 3/18/2022 #2

如果您尝试编写如下内容,则程序将无法编译(@PaulMcKenzie的评论中已经提到了这一点):

#include <iostream>
#include <ostream>

struct Person {
        int x = 0;
};

std::ostream& operator<<(std::ostream&& os, const Person& obj)
{
    return os << obj.x;
}

int main()
{
    Person p;
    std::cout << p;
}

但是,您可以通过将最后一行更改为main

std::move(std::cout) << p; // don't do this in real code

然后,程序将编译并输出 .但是,在此之后,流将处于“已移出”状态,不应再使用。0std::cout

关于一些评论,似乎对什么是 r 值引用存在误解。有些人似乎认为它只是一个更好的 l 值参考,我们在 C++11 之前无法访问。

L 值引用是指可以在表达式左侧使用的内容。您可以将其视为对实际对象的引用,可以更改并分配给该对象,依此类推。另一方面,r 值引用是指可以在赋值右侧使用的临时对象,即某物。

假设您有两个具有以下签名的重载。operator<<

std::ostream& operator<<(std::ostream& os, const Person& obj);
std::ostream& operator<<(std::ostream&& os, const Person& obj);

规则是,第一个接受 l 值或 r 值引用。后者仅接受 r 值引用。如果我们用 l 值调用运算符( 就是一个例子),将使用第一个重载。如果我们用 r 值(如 )调用运算符,则将使用第二个值。您可以省略 r 值引用重载,在这种情况下,始终选择第一个重载,但如果要调用具有 l 值的运算符,则不能省略第一个重载。但是,使用 l 值调用运算符是您几乎总是想要的:std::coutstd::move(std::cout)

std::cout << p;  // will not compile if there is only an r-value reference overload

现在,r值引用的用例是什么?它们用于在对象上实现移动语义。R 值引用用于引用对象,这些对象在当前语句之后不再有效(它们没有持久性)。虽然理论上您可以移动流,就像上面所示,但这根本没有用。流旨在用作具有较长生存时间的对象,而不是临时对象。std::cout

所以最后回答你的问题:你不应该这样做,因为流通常不用作临时对象。如果您真的想为算子提供 r 值参考重载,请继续,但也要为 的正常用例提供 l 值重载<<std::cout << p;

评论

2赞 Caleth 3/18/2022
std::cout在您的示例中未移动。你必须搬到一个不同的地方才能打破一切。osostream
0赞 Jakob Stark 3/18/2022
是的,这实际上是一个很好的观点。我习惯于认为 -ed 对象是无效的,所以我不认为这是;)但尽管如此,我还是不建议在实际代码中使用这种风格。std::move
0赞 numzero 8/4/2022 #3

我的问题是,我们是否可以(并且应该)对第一个参数使用右值引用而不是左值引用,如下所示:

friend std::ostream& operator<<(std::ostream&& os, const Person& obj);注意:第一个参数是 rvalue-reference

这在技术上是可行的(有一些限制),但绝对是非常奇怪的事情。使用右值引用通常意味着对象将被“消耗”(例如,从中移动),并且不应在后续操作中使用。但是,通常写入流不会消耗它;该流仍可用于后续写入。