iostream插入器和提取器可以是类成员而不是全局重载吗?
Could iostream inserters and extractors be class members instead of global overloads?
必须声明"全局友元操作符重载"才能进行序列化,这总是让我觉得很笨拙。必须在类之外声明序列化操作符似乎并不基本。所以我一直在寻找一个可靠的答案。
(注意:如果有人有更好的Google-Fu找到一个好的答案,我很有兴趣阅读。)
我怀疑这在技术上是可能的,只是一个符号问题。如果库被设计为对<<
和>>
进行成员重载,则必须构建从右到左的流操作行,而不是从左到右。所以我们不写
Rational r (1, 2);
cout << "Your rational number is " << r;
您必须将输出行写成:
r >> ("Your rational number is " >> cout);
需要括号来启动反向链,因为>>
和<<
从左到右关联。如果没有它们,它将尝试在"Your rational number is " >> cout
之前找到r >> "Your rational number is "
的匹配。如果选择了从右到左结合的操作符,则可以避免这种情况:
r >>= "Your rational number is " >>= cout;
(注意:在标准库内部,非类类型,如字符串文字,必须通过全局操作符重载来处理)
但是这是极限吗?对于任何想要将序列化分派到类中的iostream风格设计来说,这种反转几乎是不可避免的吗?还有其他问题吗?
UPDATE也许对这个"问题"更好的说法是,我开始怀疑以下情况:
对于希望序列化自己的非流对象,iostream库可以假设设计为插入器和提取器是类成员而不是全局重载…并且不会(显著地)影响运行时属性。然而,只有当iostream的作者愿意接受它将迫使客户端形成从右向左的流操作时,这才会起作用。
但是,对于为什么全局重载操作符和成员可以解锁从左到右(而不是从右到左)表达自己的能力,我缺乏直觉。问题是后见之明、模板或c++ 11的一些深奥特性是否可以提供替代方案。或者"c++物理"有一个固有的偏向于一个方向而不是另一个方向,而全局重载在某种程度上是书中唯一覆盖它的编译时技巧。
与弗莱明的电机左手定则相比
对于重载流操作符,标准没有限制它们应该是成员还是非成员,所以理想情况下它们可以是成员。实际上,标准库定义的大多数流输出和输入操作符都是流类的成员。
A基本原理:
为什么inserters
和extractors
没有作为成员函数重载?
如果二元操作符改变了其左操作数,通常将其设置为左操作数类型的成员函数是有用的。(因为它通常需要访问操作数的私有成员)。
根据此规则,流操作符应该作为其左操作数类型的成员来实现。然而,它们的左操作数是来自标准库的流,并且不能更改标准库的流类型。因此,当为自定义类型重载这些操作符时,它们通常被实现为非成员函数。
通过分隔<<而>>来自你正在显示的对象。
首先,您可能希望自定义显示非类类型,如enum或指针。假设你有一个类Foo,你想自定义打印一个指向Foo的指针。不能通过向Foo添加成员函数来实现。或者您可能想要显示一个模板化的对象,但只显示一个特定的模板参数。例如,您可能希望将vector
另一个原因可能是不允许或不愿意修改现有的类。这是OO设计中开放/封闭原则的一个很好的例子,您希望您的类对扩展是开放的,但对修改是封闭的。当然,你的类必须在不破坏封装的情况下公开它的一些实现,但这种情况经常发生(向量暴露它们的元素,复杂暴露Re和Im,字符串暴露c_str,等等)。
你甚至可以定义两个不同的<<对于不同模块中的同一个类,如果有意义的话。
我以前有过同样的问题,看起来插入器和提取器必须是全局的。对我来说,这还可以;我只是想避免在我的类中创建更多的"好友"函数。我是这样做的,即提供一个"<<"可以调用的公共接口:
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);
}
我还遗漏了什么问题吗?
我想不起来,但我得说这已经很糟糕了。
通常的outfile << var1 << var2 << var3;
是一种相当"线性"的语法。而且,由于在这两种情况下我们都是从左到右读取,因此名称的顺序将与它们在文件中的顺序相同。
你打算使语法非线性。人类读者将不得不跳到前面,回头看看发生了什么。这让事情变得更加困难。你可以更进一步。要阅读最后一行r >>= "Your rational number is " >>= cout;
,您首先必须通过>>=向前阅读,以查看您需要跳到最后一个单词(或附近),阅读">>= cout",跳到字符串的开头,向前阅读字符串,等等。而不是把你的眼睛从一个符号移到另一个符号,在那里大脑能够流水线整个过程。
我有几年使用这种非线性语法的语言的经验。我现在正在考虑使用clang将c++"编译"成那种语言。(不过,原因不止于此。)如果成功了,我会快乐得多。
我更喜欢的替代方法是一个非常小的操作符重载,它只调用成员函数。即使稍微优化一下,它也会从生成的可执行文件中消失。
上面有一个"明显"的例外,那就是在语句中只有一个读/写操作,如myobj >> cout;
在c++中,二进制操作符通常是非类成员。根据Bjarne Stroustrup编写的 c++编程语言操作符,规范表示是一个全局函数,它首先复制左操作数,然后对右操作数使用+=,然后返回结果。因此,让流操作符是全局的并不是什么不寻常的事情。如前所述,我们希望流操作符是流类的成员,而不是数据类的成员。
- 重载运算符 new/new[] 删除/删除[] 全局C++
- 在全局命名空间中重载不依赖于用户定义类型的标准定义类型的运算符是否格式正确?
- 重载全局运算符新(非 POD)
- 全局删除的冲突重载
- 运算符重载:简单添加...错误 C2677:二进制"+":未找到采用类型 ___ 的全局运算符(或者不存在可接受的转换)
- 全局新运算符重载
- 为什么使用范围解析运算符会更改调用全局命名空间中的哪个重载模板?
- 如何为模板类的所有实例专用化或重载全局模板函数
- 误解全局运算符重载规则
- 在重载的全局new操作符中使用静态对象会导致核心转储运行时错误
- 为什么成员函数的重载解析排除全局函数
- 每个类重载new(),而不是全局重载
- 如果存在具有不同参数类型的重载,则在全局命名空间中找不到函数
- 全局重载 delete[] 未在第三方库中调用
- 如何重载全局new操作符
- 浮点数的==和!=的全局重载
- 在c++类中重载全局函数getline
- iostream插入器和提取器可以是类成员而不是全局重载吗?
- 在全局范围内重载std容器上的操作符是不好的风格吗?
- 全局函数模板重载和const形参