Форвардные объявления вызывают ошибки после рефакторинга кода

Моя первоначальная структура класса была похожа на:

//def.h
namespace A
{
   struct X {};
}

и передайте объявления, где это необходимо:

//file that needs forward declarations
namespace A { struct X; }

После некоторого рефакторинга X был перемещен в другое пространство имен, но для того, чтобы старый код оставался «работающим», были использованы директивы using:

//def.h
namespace B
{
   struct X {};
}
namespace A
{
   using ::B::X;
}

Теперь мы можем получить доступ к тому же классу, сохраняя старый синтаксис A::X, но предварительные объявления вызывают ошибки. Вторая проблема заключается в том, что сообщение об ошибке, которое я получаю, не указывает на то, где находятся предварительные объявления, а поиск/замена предварительных объявлений занимает много времени.

На данный момент я исправил проблему (сложный способ).

Каков наилучший подход к решению этой ситуации?

ИМО, using вообще не должно быть, и весь код, использующий X, должен быть реорганизован, чтобы приспособиться к новому пространству имен (это одно из решений), но, к сожалению, это не вариант.

Фактический код намного сложнее, это упрощенный пример.


person Luchian Grigore    schedule 09.01.2013    source источник
comment
Я предполагаю, что fwd.h не является def_fwd.h, то есть заголовком, вся цель которого состоит в том, чтобы перенаправить объявление некоторых других имен заголовков?   -  person K-ballo    schedule 09.01.2013
comment
@K-ballo, это просто имя, предварительные объявления относятся к файлу реализации.   -  person Luchian Grigore    schedule 09.01.2013
comment
Что ж, это отменяет мое предложение разобраться с этим. Взято из Стандартной библиотеки (см. iosfwd), Boost (отовсюду) и т. д. др.   -  person K-ballo    schedule 09.01.2013
comment
Было ли X перемещено в существующее пространство имен B или namespace A было переименовано в B? В последнем случае не могли бы вы использовать псевдонимы пространств имен?   -  person TemplateRex    schedule 09.01.2013
comment
@rhalbersma существующее пространство имен — A все еще существует.   -  person Luchian Grigore    schedule 09.01.2013
comment
@LuchianGrigore Каковы ваши сообщения об ошибках?   -  person TemplateRex    schedule 09.01.2013
comment
@rhalbersma ideone.com/CxhctQ   -  person Luchian Grigore    schedule 09.01.2013
comment
@LuchianGrigore, вы смешиваете прямые предварительные объявления и косвенные через использование объявлений. преобразование всех предварительных объявлений в последнюю форму в одном заголовке fwd должно помочь.   -  person TemplateRex    schedule 09.01.2013
comment
@LuchianGrigore Я обновляю свой ответ стандартной цитатой о правилах поиска членов пространства имен.   -  person TemplateRex    schedule 10.01.2013


Ответы (2)


Я понимаю, что речь идет скорее о новом коде, чем о рефакторинге существующего кода, но мне нравится использовать для таких случаев специальный заголовок с именем X_fwd.hpp.

// X_def.hpp
namespace B
{
   struct X {};
}
namespace A
{
   // NOT: using namespace B; // does not participate in ADL!      
   typedef ::B::X X;  // OR: using ::B::X;
}

// X_fwd.hpp
namespace A { struct X; }

// some file needing declaration of X
#include <X_fwd.hpp>

Это значительно упрощает поиск предварительных объявлений, а также их изменение постфактум, потому что изменение изолировано только в одном месте (DRY...).

ПРИМЕЧАНИЕ 1: насколько я знаю, нет никакой технической разницы между использованием ответа Питера Вуда о typedef и вашей декларации using. Обратите внимание, что директива using using namespace B; может вызвать проблемы, поскольку они игнорируются функцией Поиск, зависящий от аргумента. Хуже того, часть вашего кода может затем даже молча вызвать неправильную перегрузку функции, потому что вы больше не используете новое пространство имен B!

ПРИМЕЧАНИЕ2. В комментариях к вопросу был приведен пример Ideone. Это прекрасно иллюстрирует тонкость поиска имен внутри пространств имен: цитата из черновика Стандарт, раздел 3.4.3.2 Члены пространства имен [namespace.qual], пункт 2

Для пространства имен X и имени m набор поиска S(X, m) с указанием пространства имен определяется следующим образом: пусть S'(X, m) будет набором всех объявлений m в X и встроенным набором пространств имен X (7.3.1). Если S'(X, m) непусто, то S(X, m) есть S'(X, m); в противном случае S(X, m) является объединением S(Ni, m) для всех пространств имен Ni, назначенных директивами использования в X и его встроенном наборе пространств имен.

Это объясняет следующую каверзную двусмысленность

namespace A
{
    struct X1{};
    struct X2{};
}

namespace B
{
    using A::X1;    // OK: lookup-set is both namespace A and B, and a single unique name is found (at this point!)
    struct X1;      // OK: lookup-set is namespace B, and a single unique name is found

    struct X2;      // OK: lookup-set is namespace B, and a single unique name is found
    using A::X2;    // error: lookup-set is both namespace A and B, and no unique name is found (at this point!)
}

Таким образом, действительность наличия как прямого объявления, так и объявления использования с одним и тем же именем внутри пространства имен зависит от порядка. Отсюда удобство единственного объявления в заголовочном файле fwd.

person TemplateRex    schedule 09.01.2013
comment
По-прежнему является ошибкой иметь объявление структуры и определение типа с общим именем. И определения типов также не влияют на ADL. - person Sebastian Redl; 09.01.2013
comment
@SebastianRedl Tnx, обновлено, чтобы отразить тонкую разницу между директивой using и объявлением typedef/using. - person TemplateRex; 09.01.2013
comment
+1 за последнюю тонкость - я видел это, когда сам исправлял проблему, но не мог объяснить, почему. - person Luchian Grigore; 10.01.2013
comment
@LuchianGrigore Мне было очень весело находить точные детали. Стандарт начинается довольно весело в разделе 3.4: правила поиска имен применяются одинаково ко всем именам [...], а затем следуют 14 (!) страниц неквалифицированных, квалифицированных, директив использования, объявлений использования и других запутанных деталей ;-) - person TemplateRex; 10.01.2013

Лучший подход — исправить код.

Вы можете сделать это в два этапа:

  1. исправить все форвардные объявления
  2. удалить using ::B::X;
person BЈовић    schedule 09.01.2013
comment
Да, я тоже так думал, и я так и сделал (без удаления директив using не вариант). - person Luchian Grigore; 09.01.2013