必须声明“全局朋友操作员重载”来进行序列化总是让我感到震惊。在类之外声明序列化运算符似乎没有基础。所以我一直在寻找一个可靠的答案。
(注意:如果有人有更好的Google-Fu来找到已经写好的答案,我有兴趣阅读。)
我怀疑它在技术上是可行的,而且只是一个符号问题。如果该库被设计为执行<<
和>>
的成员重载,则必须从右到左构建一系列流操作,而不是从左到右。所以不要写:
Rational r (1, 2);
cout << "Your rational number is " << r;
您必须将输出行写为:
r >> ("Your rational number is " >> cout);
需要使用括号来启动向后链接,因为>>
和<<
从左到右关联。没有它们,它会尝试在r >> "Your rational number is "
之前找到"Your rational number is " >> cout
的匹配项。如果选择了right-to-left associativity的运营商,则可以避免这种情况:
r >>= "Your rational number is " >>= cout;
(注意:在库内部,像字符串文字这样的非类类型必须使用全局运算符重载来处理。)
但这是极限,对于任何想要将序列化分派到课堂中的iostream式设计来说,这种逆转几乎是不可避免的吗?我错过了其他任何问题吗?
更新或许对“问题”的更好的措辞是说我开始怀疑以下内容:
对于希望自行序列化的非流对象,假设iostream库可以设计为插入器和extracters是类成员而不是全局重载......并且没有(显着)影响运行时属性。然而,如果iostream作者愿意接受它会迫使客户从右到左形成流媒体操作,那么这只会有效。
但我缺乏一种直觉,即为什么全局超载操作员与成员之间可以解锁一个可以解锁的能力,从左到右(而不是从右到左)表达自己。问题是,后见之明,模板或C ++ 11的一些深奥功能是否可以提供替代方案。或者“C ++物理学”对一个方向有另一个方面的固有偏见,而全局重载在某种程度上是本书中用于覆盖它的唯一编译时技巧。
比较答案 0 :(得分:3)
对于重载流操作符,标准没有限制它们应该是成员还是非成员,所以理想情况下它们可以是。实际上,标准库定义的大多数流输出和输入操作符都是成员流类。
基本原理:
为什么inserters
和extractors
不会作为成员函数重载?
通常,运算符重载的规则是:
如果二元运算符改变了它的左操作数,那么使它成为左操作数类型的成员函数通常很有用。(因为它通常需要访问操作数私有成员)。
通过此规则,Stream运算符应该实现为其左操作数类型的成员。但是,它们的左操作数是来自标准库的流,而One不能更改标准库的流类型。因此,当为自定义类型重载这些运算符时,它们通常被实现为非成员函数。
答案 1 :(得分:2)
通过分离&lt;&lt;的定义,您可以获得更大的灵活性和&gt;&gt;来自您正在显示的对象。
首先,您可能希望自定义显示非类类型,如枚举或指针。假设您有一个类Foo,并且您想要自定义打印指向Foo的指针。你不能通过向Foo添加成员函数来实现。或者您可能希望显示模板化对象,但仅适用于特定模板参数。例如,您可能希望显示向量&lt; int&gt;以逗号分隔的列表,但向量&lt; string&gt;作为一列字符串。
另一个原因可能是您不允许或不愿意修改现有类。这是OO设计中开放/封闭原则的一个很好的例子,您希望您的类可以打开以进行扩展,但是关闭以进行修改。当然,你的类必须暴露它的一些实现而不破坏封装,但通常就是这种情况(向量暴露它们的元素,复杂暴露Re和Im,字符串暴露c_str等)。
你甚至可以定义两个不同的重载&lt;&lt;对于不同模块中的同一类,如果有意义的话。
答案 2 :(得分:2)
之前我遇到过同样的问题,看起来插件和extracters必须是全局的。对我来说,没关系;我只想避免在班上制作更多“朋友”的功能。 这就是我这样做的方式,即提供一个“&lt;&lt;”可以打电话:
class Rock {
private:
int weight;
int height;
int width;
int length;
public:
ostream& output(ostream &os) const {
os << "w" << weight << "hi" << height << "w" << width << "leng" << length << endl;
return os;
}
};
ostream& operator<<(ostream &os, const Rock& rock) {
return rock.output(os);
}
答案 3 :(得分:1)
我错过了其他任何问题吗?
不是我能想到的,但我已经说过这已经很糟糕了。
通常outfile << var1 << var2 << var3;
是一种相当“线性”的语法。而且,由于我们在两种情况下从左到右阅读,因此名称将与文件中的名称相同。
您计划将语法设为非线性。人类读者必须跳过并回过头来看看发生了什么。这使得它变得更难。你走得更远。要阅读你的最后一行r >>= "Your rational number is " >>= cout;
,首先必须通过&gt;&gt; =向前阅读,看看你需要跳到最后一个单词(或左右),阅读“&gt;&gt; = cout”,跳回字符串的开头,通过字符串向前读取,依此类推。而不是将你的眼睛从一个标记移动到下一个标记,在那里大脑能够管理整个过程。
我对使用这种非线性语法的语言有多年的经验。我现在正在研究使用clang将C ++“编译”成该语言。 (尽管有更多的原因,但是。)如果它成功了,我会更快乐。
我首选的替代方案是一个非常小的运算符重载,它只调用一个成员函数。如果稍微优化,它将从生成的可执行文件中消失。
上面有一个“显而易见”的例外,即当你在一个语句中只有一个读/写时,就像myobj >> cout;
答案 4 :(得分:1)
在C ++中,二元运算符通常是非类成员。根据Bjarne Stroustrup运算符的 C ++编程语言,规范表示是一个全局函数,首先复制其左操作数,然后使用右操作数对其使用+ =,然后返回结果。因此让流运营商成为全球运营商并非完全不同寻常。正如Als所提到的,我们希望流操作符是流类的成员而不是数据类的成员。