Этот фрагмент не компилируется:
// 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
и использовать голый ExceptionHandler
внутри foo
однозначно ссылающийся на класс в namespace google_breakpad
.
Однако g++ и клан g++ согласны с тем, что это неверно, настаивая на интерпретации голого 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 и сделать эту программу действительной? (Без изменения каких-либо typedef google_breakpad::ExceptionHandler BreakpadEH
знаю, что я могу заменить using
с typedef google_breakpad::ExceptionHandler BreakpadEH
а затем изменить все последующие использования голого имени, но это просто перемещает проблему вокруг --- откуда я знаю, что это имя не загрязнены системными заголовками? (реальная единица перевода завершается, включая порядка 500 системных заголовков, почти все из которых специфичны для ОС и написаны людьми, которые, похоже, вообще не заботятся о загрязнении пространства имен).)
В общем случае вам не должно быть позволено объявлять новый тип с тем же именем, что и уже объявленный в той же области, но, возможно, это не так для классов и typedefs, поскольку вы можете технически проинструктировать компилятор о том, как отличить их. Если бы у вас был другой 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));
Но вы не можете повторно объявить один и тот же тип в той же области действия и рассчитывать на то, чтобы уйти с ним чисто. По крайней мере, это мое понимание этого. YMMV. :-)