繁体   English   中英

运算符如何在名称空间中重载解析?

[英]How does the operator overload resolution work within namespaces?

我发现C ++解析运算符重载的奇怪行为,我无法解释自己。 指向描述它的某个资源的指针就像答案一样好。

我有2个翻译单位。 在一个(称为util.cpp / h)中,我声明并定义了两个运算符(我省略了readabilty的实际实现,无论如何都会发生problam):

// util.h
#ifndef GUARD_UTIL
#define GUARD_UTIL

#include <iostream>

std::istream& operator>>(std::istream& is, const char* str);
std::istream& operator>>(std::istream& is, char* str);
#endif

和:

//util.cpp
#include "util.h"
#include <iostream>

std::istream& operator>>(std::istream& is, const char* str) {
  return is;  
}
std::istream& operator>>(std::istream& is, char* str) {
  return is;  
}

如果在全局命名空间中,这些运算符是因为它们在std类型和内置类型上运行,并且应该可以在任何地方使用。 它们只能从全局命名空间(例如来自main())或明确地告诉编译器它们在全局命名空间中工作(参见代码示例)。

在另一个翻译单元(称为test.cpp / h)中,我在命名空间中使用这些运算符。 这有效,直到我将类似的运算符放入此命名空间。 一旦添加此运算符,编译器(例如gcc或clang)就不能再找到可行的运算符>>。

// test.h
#ifndef GUARD_TEST
#define GUARD_TEST

#include <iostream>

namespace Namespace {
  class SomeClass {   
    public:
      void test(std::istream& is);
  };

  // without the following line everything compiles just fine
  std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
}

#endif

和:

//test.cpp
#include "test.h"
#include "util.h"
#include <iostream>

void Namespace::SomeClass::test(std::istream& is) {
  ::operator>>(is, "c"); //works
  is >> "c" //fails
}

为什么编译器在命名空间中没有operator >>但是找不到时会找到正确的运算符? 为什么运算符会影响编译器找到正确的编译器的能力,即使它具有不同的签名?

解决这个问题的一个尝试是

std :: istream&operator >>(std :: istream&is,const char * str){:: operator >>(is,str); }

进入命名空间,但链接器抱怨以前的定义。 附加:为什么链接器会找到编译器找不到的东西?

这是一个隐藏名称的问题。 标准说(c ++ 03,3.3.7 / 1)

名称可以通过嵌套声明性区域或派生类(10.2)中相同名称的显式声明来隐藏。

您的案例中的“名称”将是operator>> ,名称空间构成嵌套的声明性区域。

解决这个问题的最简单方法是使用using声明using声明namespace-local operator<<

namespace your_namespece {
    std::istream& operator>>(std::istream& is, SomeClass& obj) { return is; }; 
    using ::operator>>;
}

请注意,此功能不会干扰Koenig查找(至少在您的情况下,原则上它可以),因此仍然可以找到std:: IO运算符。

PS:另一种解决这个问题的可能性是将SomeClass的运算符定义为内联friend 这些函数在命名空间级别(在“他们的”类之外)声明,但从那里看不到。 它们只能通过Koenig查找找到。

这里有几个问题; 对于初学者,您正在重新定义std::中已存在的全局命名空间中的函数。 但是,您描述的问题是由于名称查找的工作方式。 基本上,在运算符重载的情况下,编译器会执行两次名称查找。 第一个(用于所有符号,不仅仅是运算符)从符号出现的范围开始,向外工作:首先是本地块,然后是类,它的基类(如果有的话),最后是名称空间,到全局命名空间。 这种查找的一个重要特征是它在它找到名称的任何范围内停止:如果它在本地范围内找到一个名称,它就不会查找任何类; 如果它在类中找到一个,它不会查找基类或命名空间,如果它在命名空间中找到一个,则它不会查找任何封闭的命名空间。 就此查询而言,所有重载必须在同一范围内。 第二个查找仅影响函数和运算符重载,并发生在用作参数的类或对象的上下文中; 因此,如果其中一个操作数是标准库中的一个类(或从标准库中的类派生的任何内容),编译器将在std::查找函数,即使使用该符号的上下文不包括std:: 您遇到的问题是内置类型(如char* )并不暗示任何命名空间(甚至不是全局):给定您的重载,第一个查找将停在第一个operator>>它看到,第二个查找将停止只会查看std:: 你的功能都不是。 如果要查找重载运算符,则必须在其某个操作数的范围内定义它。

具体来说,这里:你不能重载std::istream& operator>>( std::istream&, char* ) ,因为它已经在标准库中重载了。 std::istream& operator>>( std::istream&, char const* )是可能的,但我不确定它应该做什么,因为它无法写入第二个操作数。 更一般地说,您应该只为您定义的类型重载此运算符,并且您应该将重载放在与类型本身相同的命名空间中,以便通过上面的第二个查找找到它(称为参数依赖查找或ADL) - 或更早,Koenig查找,发明它的人之后)。

::是全局范围,因此,编译器必须扫描全局命名空间并找到此运算符。 是>>“C”,试图在Namespace中找到operator >>,所以,编译器找到并停止搜索,然后编译器尝试选择需要签名的运算符,如果没有这样的运算符 - 编译器失败。 我想你应该阅读Herb Sutter Exceptional C ++。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM