在浏览SO时,我经常会发现涉及重载/定义std::ostream& operator<<(std::ostream& os, const Foo& foo)
或Foo operator+(const Foo& l, const Foo& r)
的问题或答案。
虽然我知道如何以及何时(不)编写这些运算符,但我对namespace
事情感到困惑。
如果我有以下课程:
namespace bar
{
class Foo {};
}
我应该在哪个namespace
编写不同的运算符定义?
// Should it be this
namespace bar
{
std::ostream& operator<<(std::ostream& os, const Foo& foo);
}
// Or this ?
namespace std
{
ostream& operator<<(ostream& os, const bar::Foo& foo);
}
// Or this ?
std::ostream& operator<<(std::ostream& os, const bar::Foo& foo);
同样的问题适用于operator+
。那么,这里的好习惯是什么?为什么?
答案 0 :(得分:13)
规则是,在寻找合适的函数重载时,会考虑当前命名空间和参数类型定义的所有命名空间。这称为Argument Dependent Lookup(ADL)。
所以当你有这个代码时:
::std::ostream& os = /* something */;
const ::bar::Foo& foo = /* something */;
os << foo;
考虑以下名称空间:
因此,您所命名的所有三种可能性都会起作用,因此乍一看“足够好”。
然而....
不允许在:: std中定义新函数,因此不能将重载运算符放在该命名空间中。 (你可以在:: std中专门化模板,但这不是我们在这里做的事情)
其次,“当前命名空间”可能会更改,因此如果将函数定义放在该命名空间中,则可能无法始终找到它。
所以最后,放置重载运算符的最佳位置与Foo在同一名称空间中:
namespace bar
{
std::ostream& operator<<(std::ostream& os, const Foo& foo);
}
答案 1 :(得分:11)
它应该在bar
命名空间中。您必须考虑what makes up the interface for the class,并将它们组合在一起。
“一个类描述了一组数据以及对该数据进行操作的函数。”您的免费功能在Foo
上运行,因此它是Foo
的一部分。它应与命名空间Foo
中的bar
分组。
Argument-dependent lookup或ADL会找到该功能。
我们也知道我们应该prefer non-friend non-member functions。这意味着,通常,您的类将具有其定义和成员函数,紧接着是在类上运行的自由函数。
答案 2 :(得分:2)
要使操作符重载正常工作,该函数必须是 在与其操作数之一相同的命名空间中。否则,ADL 找不到。这意味着您的类的命名空间 像+和 - 这样的运营商。从理论上讲,你可以把运算符&lt;&lt; 在std或与您的类相同的命名空间中,但是 标准禁止在std中定义新函数,所以在这里也是如此 将它放在与类相同的命名空间中。
(当然,你通常不会实现+或 - ,而是+ =和 - =,然后从提供+和的模板派生 - 自动。)
答案 3 :(得分:0)
最佳选择是选项1。 为什么?因为当您使用非限定函数名称(重载运算符是函数)时,除了正常的名称查找之外,还应用了Argument-Dependent查找,即(非正式地)搜索声明参数的所有名称空间。 E.g。
namespace N
{
class X(){};
void f(X){}
}
int main()
{
N::X x;
f(x); //works fine, no need to qualify f like N::f
}
运营商也一样。
另一方面,在选项2的情况下,仍然会找到运算符,因为ostream处于std(相同的ADL规则)。但是将内容添加到std名称空间并不是一个好主意。
第三种选择是不好的,风格上 - 为什么如果第一种选择就足够了呢?
所以,绝对是选项1。
HTH。
答案 4 :(得分:0)
好的做法是将(非成员)运算符声明为与其所属接口的类相同的名称空间。
对于像operator+
这样的东西,这很容易:它只在Foo对象上运行,因此它应该与Foo本身位于同一个名称空间中。
对于operator<<
和operator>>
,您仍然可以在名称空间std
和bar
之间进行选择。首先,您不应该向命名空间std
添加函数/运算符重载。其次,这些重载的重要部分不是它们与流一起工作,而是它们读/写一个Foo对象。因此将它与Foo类捆绑在一起更有意义。
还应该注意的是,C ++的规则被设计成使得在与它们操作的类相同的命名空间中定义的重载操作符几乎总是被正确地找到,而如果这样做会更频繁地出错。运算符在其他一些不相关的命名空间中声明。