Идиома pimpl с использованием структуры анонимного пространства имен: это безопасно?

Мой товарищ по команде регулярно использует вариацию на pimpl, которая ему нравится:

Foo.h:

namespace { struct Impl; }

class Foo
{
public:
  Foo();
  ~Foo();

  void Bar(int n);
  /* ... */

private:
  std::unique_ptr<Impl> _impl;
};

Здесь происходит то, что он заранее объявляет, что класс реализации находится в анонимном пространстве имен. Затем он определит класс Impl в Foo.cpp.

Таким образом, определение структуры ::Impl будет доступно для единицы перевода Foo.cpp. Другой код, включающий Foo.h, вызовет предупреждение, потому что они, очевидно, не могут получить доступ к ::Impl, определенному в Foo.cpp. Но тогда они нам и не нужны - это класс, предназначенный для использования только в Foo.cpp; мы не хотим, чтобы он был виден или известен где-либо еще.

И хотя мы, безусловно, можем иметь ситуацию, когда в .cpp файл включается несколько заголовков, каждый из которых объявляет свои собственные ::Impl структуры, на самом деле они не конфликтуют, потому что структуры никогда не используются за пределами соответствующих единиц перевода.

tl; dr: Это выглядит странно, вызывает предупреждения и выглядит так, как будто может вызвать конфликты, но, похоже, на самом деле работает.


Все это, как говорится, мне неудобно, когда что-то, вызывающее предупреждения, так глубоко встроено в наш код (чем больше это файлов заголовков, тем сложнее будет удалить). Это также просто тонна предупреждений.

Мой товарищ по команде поддерживает это, потому что это просто, сохраняет простые определения кода и позволяет нам использовать короткое согласованное имя класса Impl во всем нашем коде.

Я не сторонник соглашений о кодировании; если это хорошая практика для нашего случая использования, я не возражаю. Но я хотел бы чувствовать себя комфортно, потому что это безопасно и ремонтопригодно, и не собирается взорваться в какой-то момент.


person Ziv    schedule 25.09.2016    source источник
comment
Дело в том, что я думаю, что нет ::Impl. Использование ::Impl пытается получить доступ к символу Impl в глобальном пространстве имен, но его нет в глобальном пространстве имен. Другой распространенный способ - сделать структуру Impl закрытым членом самого класса.   -  person Some programmer dude    schedule 25.09.2016
comment
Если это происходит в файле заголовка, который используется несколькими единицами перевода, я думаю, что это нарушение ODR. Каждое анонимное пространство имен отличается в каждой единице трансляции, поэтому класс Foo не будет иметь единообразного определения для всех единиц трансляции.   -  person Vaughn Cato    schedule 25.09.2016
comment
См. stackoverflow.com/a/23653494/951890   -  person Vaughn Cato    schedule 25.09.2016
comment
@VaughnCato: Несогласованность, похоже, находится на уровне на какой класс указывает указатель _impl, доступ к которому возможен только из собственной реализации Foo. Это фактическое нарушение ODR?   -  person Ziv    schedule 25.09.2016
comment
@Ziv: Несоответствие находится в имени Impl, которое либо означает some_anonymous_namespace1 :: Impl, либо some_anonymous_namespace2 :: Impl, в зависимости от того, в какой единице перевода вы находитесь, но это нарушение ODR, поскольку все имена должны иметь одинаковое значение во всех переводах единицы.   -  person Vaughn Cato    schedule 25.09.2016
comment
@JoachimPileborg: Вы предлагаете сделать Impl вложенным классом / структурой? Я думал, вы не можете пересылать-объявить вложенный класс (и если вы не можете переадресовать-объявить это, это бесполезно для pimpl).   -  person Ziv    schedule 25.09.2016
comment
@VaughnCato: Я определенно вижу, как это выглядит как плохая практика и неоднозначное определение. Я думаю, что мой товарищ по команде отклоняет это, поскольку конфликтующее определение - это просто указатель type, который не влияет на размер и не должен (?) Влиять на поведение, поскольку только Foo.cpp может действительно получить доступ к указателю. Но я согласен, с любыми неточностями играть опасно.   -  person Ziv    schedule 25.09.2016
comment
@Ziv Ха, конечно же, можете   -  person Barry    schedule 25.09.2016
comment
@Ziv Вам нужно сделать предварительное объявление внутри класса. Например. class Foo { struct Impl; public: ... };   -  person Some programmer dude    schedule 25.09.2016
comment
Спасибо вам всем. Я узнал много полезного :)   -  person Ziv    schedule 25.09.2016


Ответы (1)


Класс Foo нарушает ODR. Каждый файл cpp считает, что его уникальный ptr содержит другой тип.

Нарушение ODR делает вашу программу плохо сформированной, не требует диагностики.

Ваша программа может работать, но ее поведение полностью не определено стандартом C ++.

Практическая проблема, которая может возникнуть, заключается в том, что компилятор может измениться у вас под ногами, и текущее неопределенное поведение («похоже, работает») изменится на что-то другое («неожиданный сбой приведения», «повреждение таблиц типов», «компоновщик не может ссылка "," компилятор доказывает, что класс никогда не может использоваться за пределами реализованной единицы трансляции, и стирает весь код в ваших функциях, как если бы они выполнялись, это был бы UB. ") в качестве примеров, но нет никаких ограничений на то, насколько безумно это могло быть получить.

Иногда польза от УБ стоит того, чтобы рисковать. Я не вижу здесь никакой пользы.

Создайте namespace FooImpl или Foo_details и вставьте туда Impl.

person Yakk - Adam Nevraumont    schedule 25.09.2016