静态与成员运算符重载:std::operator<< 和 std::ostream::operator<<
Static vs. Member Operator Overloads: std::operator<< and std::ostream::operator<<
C++ 的 ostream
class 为 operator<<
提供了许多默认重载,但它们的定义方式并不完全相同。
char
类型、string
类型和右值流的 overloads 定义为自由 namespace
作用域函数,例如:
namespace std {
ostream &operator<<(ostream &os, char c);
}
虽然算术类型的overloads、streambuf
和流操作符被定义为std::ostream
的成员函数,例如:
namespace std {
ostream &ostream::operator<<(int val);
}
我的问题
这种区别有什么原因吗?我知道对这些运算符重载的调用略有不同(即 ADL 用于自由 namespace
-范围定义),因此我想可能出于优化目的而偏好特定类型的运算符重载。但是这里 std::ostream
对不同的类型使用了两种类型的定义。这种允许的语义或实现优化有什么优势吗?
I'd imagine there might be a preference to a particular type of operator overload for optimization purposes
嗯,不。归根结底,两者都被执行为函数调用。重载决议本身甚至没有明显的暗示。由于标准规定在 [over.match],段落 2 and 6:
If either operand has a type that is a class or an enumeration, a
user-defined operator function might be declared that implements this
operator or a user-defined conversion can be necessary to convert the
operand to a type that is appropriate for a built-in operator. In this
case, overload resolution is used to determine which operator function
or built-in operator is to be invoked to implement the operator.
The set of candidate functions for overload resolution is the union of
the member candidates, the non-member candidates, and the built-in
candidates. The argument list contains all of the operands of the
operator. The best function from the set of candidate functions is
selected according to [over.match.viable] and [over.match.best].
所有这些运算符重载都一起解决了。唯一的语义差异是从 ostream
派生的 class 可以选择 隐藏 某些成员重载。这是根据重载在派生 class 中的工作方式完成的。只有明确声明的重载才适用。与那些成员不同,自由函数重载将始终参与重载决策,即使对于从 ostream
.
派生的 classes 也是如此
由于需要将派生的 class 转换为 ostream&
以便选择自由函数重载,因此需要对它自己的隐式转换序列进行排序。如果所有重载都是自由函数,则可能会导致歧义。
因此,考虑的很可能是将可能导致歧义的类型(指针和算术类型)与我们可能始终希望可用的有用类型(指向 C 字符串和单个字符的指针)分开。并允许隐藏 "less useful" 以避免那些歧义。
正如 W.F 指出的那样。 ostream
实际上是 basic_ostream<char>
。免费功能恰好适用于仅需要流式传输的数据。本机流中的字符或字符串 "alphabet"。所以对于 basic_ostream<wchar_t>
那些自由函数将接受 wchar_t
和 wchar_t*
。简单的流式传输很可能不需要访问流专用部分。
其他重载用于在流式传输之前需要序列化的数据。由于所述序列化与流内部状态紧密耦合,因此使这些重载成为成员更有意义。
C++ 的 ostream
class 为 operator<<
提供了许多默认重载,但它们的定义方式并不完全相同。
char
类型、string
类型和右值流的 overloads 定义为自由 namespace
作用域函数,例如:
namespace std {
ostream &operator<<(ostream &os, char c);
}
虽然算术类型的overloads、streambuf
和流操作符被定义为std::ostream
的成员函数,例如:
namespace std {
ostream &ostream::operator<<(int val);
}
我的问题
这种区别有什么原因吗?我知道对这些运算符重载的调用略有不同(即 ADL 用于自由 namespace
-范围定义),因此我想可能出于优化目的而偏好特定类型的运算符重载。但是这里 std::ostream
对不同的类型使用了两种类型的定义。这种允许的语义或实现优化有什么优势吗?
I'd imagine there might be a preference to a particular type of operator overload for optimization purposes
嗯,不。归根结底,两者都被执行为函数调用。重载决议本身甚至没有明显的暗示。由于标准规定在 [over.match],段落 2 and 6:
If either operand has a type that is a class or an enumeration, a user-defined operator function might be declared that implements this operator or a user-defined conversion can be necessary to convert the operand to a type that is appropriate for a built-in operator. In this case, overload resolution is used to determine which operator function or built-in operator is to be invoked to implement the operator.
The set of candidate functions for overload resolution is the union of the member candidates, the non-member candidates, and the built-in candidates. The argument list contains all of the operands of the operator. The best function from the set of candidate functions is selected according to [over.match.viable] and [over.match.best].
所有这些运算符重载都一起解决了。唯一的语义差异是从 ostream
派生的 class 可以选择 隐藏 某些成员重载。这是根据重载在派生 class 中的工作方式完成的。只有明确声明的重载才适用。与那些成员不同,自由函数重载将始终参与重载决策,即使对于从 ostream
.
由于需要将派生的 class 转换为 ostream&
以便选择自由函数重载,因此需要对它自己的隐式转换序列进行排序。如果所有重载都是自由函数,则可能会导致歧义。
因此,考虑的很可能是将可能导致歧义的类型(指针和算术类型)与我们可能始终希望可用的有用类型(指向 C 字符串和单个字符的指针)分开。并允许隐藏 "less useful" 以避免那些歧义。
正如 W.F 指出的那样。 ostream
实际上是 basic_ostream<char>
。免费功能恰好适用于仅需要流式传输的数据。本机流中的字符或字符串 "alphabet"。所以对于 basic_ostream<wchar_t>
那些自由函数将接受 wchar_t
和 wchar_t*
。简单的流式传输很可能不需要访问流专用部分。
其他重载用于在流式传输之前需要序列化的数据。由于所述序列化与流内部状态紧密耦合,因此使这些重载成为成员更有意义。