12

可能重复:
非成员运算符重载应该放在哪里?

在浏览 SO 时,我经常发现涉及重载/定义 astd::ostream& operator<<(std::ostream& os, const Foo& foo)或 a 的问题或答案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+. 那么,这里有什么好的做法,为什么

4

5 回答 5

15

规则是,在寻找合适的函数重载时,会同时考虑当前命名空间和参数类型定义的所有命名空间。这称为参数相关查找 (ADL)。

所以当你有这个代码时:

  ::std::ostream& os = /* something */;
  const ::bar::Foo& foo = /* something */;
  os << foo;

考虑以下命名空间:

  • 当前命名空间
  • ::std,因为那里定义了操作系统的类型
  • ::bar,因为 foo 的类型是在那里定义的

因此,您命名的所有三种可能性都将起作用,因此乍一看“足够好”。

然而....

不允许在 ::std 中定义新函数,因此不能将重载的运算符放在该命名空间中。(您可以在 ::std 中专门化模板,但这不是我们在这里所做的)

其次,“当前命名空间”可能会改变,所以如果你把你的函数定义放在那个命名空间中,它可能不会总是被找到。

所以最后,将重载运算符放在与 Foo 相同的命名空间中的最佳位置:

namespace bar   
{   
  std::ostream& operator<<(std::ostream& os, const Foo& foo);   
}   
于 2010-10-08T14:23:17.857 回答
13

它应该在bar命名空间中。您必须考虑什么构成了类的接口,并将它们组合在一起。

“一个类描述了一组数据以及对该数据进行操作的函数。” 您的自由功能在 a 上运行Foo,因此它是 的一部分Foo。它应该Foo在命名空间中分组bar

依赖于参数的查找或 ADL 将找到该函数。

我们也知道我们应该更喜欢非朋友非成员函数。这意味着,一般来说,您的类将具有它们的定义和成员函数,然后是对类进行操作的自由函数。

于 2010-10-08T14:19:13.760 回答
2

为了使运算符重载正常工作,函数必须与其操作数之一位于同一命名空间中。否则,ADL 找不到它。这意味着像 + 和 - 这样的运算符的类的命名空间。从理论上讲,您可以将 operator<< 放在 std 或与您的类相同的命名空间中,但是标准禁止在 std 中定义新函数,所以在这里,您也可以将它放在与类相同的命名空间中。

(当然,您通常不会实现 + 或 -,而是 += 和 -=,然后从自动提供 + 和 - 的模板派生。)

于 2010-10-08T15:06:49.757 回答
0

最好的选择是选项 1。为什么?因为当您使用非限定函数名(重载运算符是函数)时,除了正常的名称查找外,还会应用参数依赖查找,即(非正式地)搜索声明参数的所有命名空间。例如

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。

于 2010-10-08T14:26:39.603 回答
0

好的做法是在与其接口所属的类相同的命名空间中声明(非成员)运算符。

对于类似的东西operator+,这很容易:它只对 Foo 对象进行操作,所以它应该与 Foo 本身在同一个命名空间中。对于operator<<and operator>>,您仍然可以在命名空间stdbar. 首先,您不应该将函数/运算符重载添加到 namespace std。其次,这些重载的重要部分不是它们与流一起工作,而是它们读/写一个 Foo 对象。所以将它与 Foo 类捆绑在一起更有意义。

还应该注意的是,C++ 的规则是这样设计的,即定义在与它们所操作的类相同的命名空间中的重载运算符几乎总能被正确找到,而如果声明了运算符,这将更频繁地出错在其他一些不相关的命名空间中。

于 2010-10-08T14:30:31.970 回答