【问题标题】:How does the operator overload resolution work within namespaces?运算符重载解析如何在命名空间中工作?
【发布时间】:2012-07-06 18:09:34
【问题描述】:

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

我有 2 个翻译单元。在一个(称为 util.cpp/h)中,我声明并定义了两个运算符(为了便于阅读,我省略了真正的实现,无论如何都会出现问题):

// 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;  
}

这些运算符当然是在全局命名空间中,因为它们对标准类型和内置类型进行操作,并且应该可以在任何地方使用。它们可以在全局命名空间(例如从 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
}

为什么在 Namespace 中没有 operator>> 时编译器能找到正确的 operator>> 但有一个时却找不到?为什么操作符会影响编译器找到正确的能力,即使它有不同的签名?

解决此问题的一种尝试是放置

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

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

【问题讨论】:

  • 您是否尝试过将 Namespace::operator >> 声明为“内联”
  • 顺便说一句,您的班级声明后缺少;
  • 添加了 ;.这是我常犯的错误……
  • @bert-jan 不,因为在我的实际实现中,输入流是用一些代码解析的,我发现很多代码需要内联。

标签: c++ operators overload-resolution


【解决方案1】:

:: 是全局作用域,所以编译器必须扫描全局命名空间并找到这个操作符。 是>>“C”,试图在命名空间中找到运算符>>,因此,编译器找到它并停止搜索,然后编译器尝试选择具有所需签名的运算符,如果没有这样的运算符 - 编译器失败。 我认为您应该阅读 Herb Sutter Exceptional C++。

【讨论】:

    【解决方案2】:

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

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

    您的案例中的“名称”为operator&gt;&gt;,命名空间构成嵌套的声明区域。

    解决此问题的最简单方法是使用 using 声明,在其中声明命名空间本地 operator&lt;&lt;

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

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

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

    【讨论】:

    • 这是一个有用的概念吗?我的意思是我理解它,但是如果没有适合的签名,到处看看不是更有用吗? (这不应该冒犯答案,而是 c++ 标准:))
    • @hildensia 到处查看有点违背命名空间的目的,并且由于名称冲突,您可能会捡起从未听说过的东西。
    • @hildensia:我真的不知道这种特性的原因,但我猜它来自 C 的名称隐藏传统(内部范围中的变量隐藏外部范围中的变量)范围),这实际上是恕我直言的正确做法。然后,您必须考虑诸如“如果我在内部范围中有char* a,在外部范围中有int a,是否应该在int 适合的地方找到外部a?”我认为名称隐藏过程实际上为您提供了一个更可预测的环境,例如。不完美的重载匹配不会被在外部范围内声明的东西干扰。
    【解决方案3】:

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

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

    【讨论】:

    • 它实际上是为了从输入流中解析。在你写os &lt;&lt; "[" &lt;&lt; matrix &lt;&lt; "]";的地方你可以简单地写is &gt;&gt; "[" &gt;&gt; matrix &gt;&gt; "]";。它应该是遗留代码中的一些“语法糖”,我必须使用。我想清理一些东西,让它用clang编译并遇到这个问题。
    • 您想要精确匹配,而不将结果存储在任何地方?这听起来像是一个机械手,而不是一个提取器。但是你必须写is &gt;&gt; match("[") &gt;&gt; matrix &gt;&gt; match("]"),或者类似的东西。您尝试执行此操作的方式的问题是 operator&gt;&gt;(istream&amp;, char*) 已经具有定义的含义,并且如果重载完全不相关,则 const 重载(char const*char*)将导致无穷无尽的混乱事物。因此,尽管我喜欢您尝试实现的语法,但我不确定这是一个好主意。
    • 这有点令人困惑,你是对的,但这实际上不在我的范围内改变......但我会删除非常量的东西,因为它无论如何都不会使用。它实际上是为了匹配 istream 并将字符串扔掉。如果您有一些协议开销,这很方便。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 2010-10-11
    • 1970-01-01
    • 2010-09-15
    • 2011-12-03
    • 1970-01-01
    相关资源
    最近更新 更多