2

必须声明“全局友元运算符重载”来进行序列化总是让我觉得很笨拙。必须在类之外声明序列化运算符似乎不是基础。所以我一直在寻找一个可靠的答案来解释为什么。

(注意:如果有人有更好的 Google-Fu 来找到已经写好的好的答案,我很想读一读。)

我怀疑它在技术上是可行的,并且只是一个符号问题。<<如果该库被设计为执行and的成员重载>>,则您必须从右到左构建一系列流操作,而不是从左到右。所以不要写:

Rational r (1, 2);
cout << "Your rational number is " << r;

您必须将输出行编写为:

r >> ("Your rational number is " >> cout);

需要括号来启动反向链接,因为>><<关联从左到右。没有它们,它将尝试为r >> "Your rational number is "before找到匹配项"Your rational number is " >> cout。如果选择了具有从右到左关联性的运算符,则可以避免这种情况:

r >>= "Your rational number is " >>= cout;

(注意:在库中,像字符串文字这样的非类类型必须通过全局运算符重载来处理。)

但这是限制吗?对于任何希望将序列化分派到类中的 iostream 风格的设计来说,这种逆转几乎是不可避免的?我错过了任何其他问题吗?


更新也许“问题”的更好措辞是说我开始怀疑以下内容:

对于希望序列化自己的非流对象,iostream 库可以假设已设计为插入器和提取器是类成员而不是全局重载......并且不会(显着)影响运行时属性。然而,这只有在 iostream 作者愿意接受它会迫使客户端从右到左形成流操作时才有效。

但是我缺乏直觉,为什么全局重载运算符与成员可以解锁从左到右(而不是从右到左)表达自己的其他解锁能力。问题是后见之明、模板或 C++11 的某些深奥特性是否可以提供替代方案。或者,“C++ 物理”是否对一个方向有内在的偏见,而全局重载在某种程度上是本书中唯一用于覆盖它的编译时技巧。

弗莱明的电机左手定则比较

4

5 回答 5

3

对于重载流操作符,标准没有限制它们应该是成员还是非成员,所以理想情况下它们可以。事实上,标准库定义的大多数流输出和输入操作符都是流的成员类。

理由:

为什么insertersextractors作为成员函数重载?

通常运算符重载的规则是:

如果二元运算符更改其左操作数,通常将其设为左操作数类型的成员函数很有用。(因为它通常需要访问操作数的私有成员)。

根据这条规则,流操作符应该作为其左操作数类型的成员来实现。但是,它们的左操作数是来自标准库的流,并且 One 不能更改标准库的流类型。因此,在为自定义类型重载这些运算符时,它们通常被实现为非成员函数。

于 2011-10-22T19:45:16.267 回答
2

通过将 << 和 >> 的定义与您正在显示的对象分开,您可以获得更大的灵活性。

首先,您可能希望自定义显示非类类型,如枚举或指针。假设你有一个类 Foo,并且你想自定义打印一个指向 Foo 的指针。你不能通过向 Foo 添加成员函数来做到这一点。或者您可能希望显示模板化对象,但仅针对特定模板参数。例如,您可能希望将 vector<int> 显示为逗号分隔列表,但将 vector<string> 显示为字符串列。

另一个原因可能是您不允许或不愿意修改现有类。这是 OO 设计中开放/封闭原则的一个很好的例子,您希望您的类对扩展开放但对修改关闭。当然,你的类必须在不破坏封装的情况下公开它的一些实现,但通常情况是这样(向量公开它们的元素,复杂公开 Re 和 Im,字符串公开 c_str 等)。

如果有意义的话,你甚至可以为不同模块中的同一个类定义两个不同的 << 重载。

于 2011-10-23T20:05:27.503 回答
2

我之前也有同样的问题,看起来插入器和提取器必须是全局的。对我来说,没关系;我只是想避免在我的课堂上产生更多的“朋友”功能。我就是这样做的,即提供一个“<<”可以调用的公共接口:

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);
    }
于 2013-06-16T01:09:58.197 回答
1

我错过了任何其他问题吗?

不是我能想到的,但我会说这已经很糟糕了。

通常outfile << var1 << var2 << var3;是相当“线性”的语法。而且,由于我们在这两种情况下都是从左到右读取的,因此名称的顺序与它们在文件中的顺序相同。

您打算使语法非线性。人类读者将不得不跳过并回来查看正在发生的事情。这使得它更难。你走得更远。要阅读最后一行,r >>= "Your rational number is " >>= cout;您首先必须通过 >>= 向前阅读,以查看您需要跳到最后一个单词(或附近),阅读“>>= cout”,跳回字符串的开头,向前读取字符串,依此类推。与将您的眼睛从一个标记移动到另一个标记相反,大脑能够将整个过程流水线化。

我在使用这种非线性语法的语言方面有几年的经验。我现在正在研究使用 clang 将 C++“编译”成该语言。(不过,还有更多的原因。)如果它成功了,我会更快乐。

我首选的替代方案是一个非常小的运算符重载,它只调用一个成员函数。如果稍微优化一下,它无论如何都会从生成的可执行文件中消失。

上面有一个“明显”的例外,即当您在语句中只有一个读/写时,如myobj >> cout;

于 2011-10-22T22:52:55.613 回答
1

在 C++ 中,二元运算符通常是非类成员。根据Bjarne Stroustrup operator+ 的C++ 编程语言,规范表示是一个全局函数,它首先复制其左操作数,然后将 += 与右操作数一起使用,然后返回结果。所以让流操作符是全局的并没有什么不寻常的。正如 Als 所提到的,我们希望流运算符是流类的成员,而不是数据类的成员。

于 2011-10-24T03:25:55.773 回答