使用foo :: bar不会屏蔽typedef-name :: bar?

时间:2014-08-30 15:28:37

标签: c++ namespaces

此片段无法编译:

// application code
namespace google_breakpad {
  class ExceptionHandler {
    ExceptionHandler(const char *, int);
  };
}
extern void bar(google_breakpad::ExceptionHandler *);

// from an unavoidably included system header
typedef int (*ExceptionHandler)(void *);

// more application code...
using google_breakpad::ExceptionHandler;
void foo(const char *s)
{
  bar(new ExceptionHandler(s, 0));
}

正如名字所暗示的那样,这是从真实的节目中删除的。我的期望是,无论哪些系统头可能已转储到全局命名空间,using google_breakpad::ExceptionHandler都会禁止任何名为ExceptionHandler其他类型,并使用裸{{1}在ExceptionHandlerfoo明确地引用了namespace google_breakpad中的类。

然而,g ++和clang ++同意这是无效的,坚持将裸ExceptionHandler解释为对全局命名空间中typedef-name的引用。

$ clang++ -fsyntax-only -std=c++11 test.cc
test.cc:14:11: error: excess elements in scalar initializer
  bar(new ExceptionHandler(s, 0));
          ^                 ~~~

$ g++ -fsyntax-only -std=c++11 test.cc
test.cc: In function ‘void foo(const char*)’:
test.cc:14:32: error: new initializer expression list treated as compound expression [-fpermissive]
   bar(new ExceptionHandler(s, 0));
                                ^
test.cc:14:32: error: invalid conversion from ‘int’ to ‘ExceptionHandler {aka int (*)(void*)}’ [-fpermissive]
test.cc:14:33: error: cannot convert ‘int (**)(void*)’ to ‘google_breakpad::ExceptionHandler*’ for argument ‘1’ to ‘void bar(google_breakpad::ExceptionHandler*)’
   bar(new ExceptionHandler(s, 0));
                                 ^

-std=c++11实际上并不影响编译器的行为,但这就是原始程序的编译方式。)

Q1:编译器的行为是否正确?

Q2:假设它们是,有没有办法抑制不需要的typedef-name,这样使这个程序有效? (不改变任何名称。我知道我可以用using替换typedef google_breakpad::ExceptionHandler BreakpadEH,然后更改所有后续使用的裸名称,但这只会解决问题 - 我怎么知道 名称是不是被系统标题污染了?(真正的翻译单元最多包括500个系统标题的顺序,几乎所有系统标题都是特定于操作系统的,并且由看似没有关心的人编写关于命名空间污染。))

1 个答案:

答案 0 :(得分:1)

一般情况下,不应该允许声明一个与同一作用域中已声明的新类型具有相同名称的新类型,但我想可能这对于类与typedef不同,因为您可以在技术上指示编译器如何区分它们。例如,如果您有另一个typedef并尝试使用using关键字,则会抱怨它已被声明。在类似的类型上使用using关键字不会覆盖旧类型,而是像向当前范围添加声明,因此编译器不必为此搜索远。你仍然无法重新声明相同的类型,所以你不能指望这个工作:

typedef int A
typedef char A;

或者

namespace X {
  typedef int A;
}
typedef char A;
using X::A;

重新声明这样的类似乎有些奇怪,因为这不起作用:

typedef int A;
class A;

但这很好用

typedef int A;
namespace X {
  class A;
}
using X::A;

当然,这就是你的错误所在。你在技术上重新宣布了这种类型,这种情况真的不应该发生,但它似乎准备与一些黑客一起工作。

无论如何,你有几个选择,其中没有一个可能正是你想要的。您可以强制编译器查找“类”选项:

bar(new class ExceptionHandler(s, 0));

或者,您可以在函数中使用using语句,因为ExceptionHandler尚未在该范围内声明:

void foo(const char *s)
{
  using google_breakpad::ExceptionHandler;
  bar(new ExceptionHandler(s, 0));
}

或者您可以将foo放入命名空间并将其调出:

namespace google_breakpad {
  void foo(const char *s)
  {
    bar(new ExceptionHandler(s, 0));
  }
}
using google_breakpad::foo;

或者您可以使用命名空间标识符:

bar(new google_breakpad::ExceptionHandler(s, 0));

但是你不能在相同的范围内重新声明相同的类型,并希望能够干净利落地使用它。至少这是我对它的理解。因人而异。 : - )