Скрытие класса C ++ в заголовке без использования безымянного пространства имен

Я пишу заголовок C ++, в котором я определяю

class A {
   // ...
};

что я хотел бы скрыть от внешнего мира (потому что он может быть изменен или даже удален в будущих версиях этого заголовка).

Также есть класс B в том же заголовке, который имеет объект класса A в качестве члена:

class B {
public:
   // ...

private:
   A a_;
};

Как правильно спрятать класс А от внешнего мира?

Если я помещаю определение A в безымянное пространство имен, компилятор выдает предупреждение, поэтому я предполагаю, что из-за проблем с внутренней связью я должен сделать что-то еще.


person Bjoern    schedule 25.04.2011    source источник


Ответы (7)


Вы можете создать внутренний класс:

class B
{
  class A { /* ... */ };
  A a_;
}
person Karl von Moor    schedule 25.04.2011
comment
О да, это, конечно, сработает. Извините, я забыл упомянуть, что B - это шаблон класса (со многими параметрами), и я сам реализую конструктор копирования и оператор присваивания A, поэтому было бы некрасиво, если бы класс A (как вложенный класс) зависел от всего шаблона параметры B. Может быть, у вас есть другое предложение? - person Bjoern; 25.04.2011
comment
@Bjoern Я не уверен, полностью ли я вас понял. template<typename T, typename U> class B и внутри class A { A(const A&); A& operator=(const A&); }; не было бы проблем, верно? Я не понимаю, что вы имеете в виду под классом A, зависит от параметров шаблона B. - person Karl von Moor; 25.04.2011
comment
Вовсе нет, но typename B<T1,U1>::A и typename B<T2,U2>::A тогда будут разными, хотя A на самом деле не зависит от каких-либо параметров шаблона B. Для этого я должен написать class A { template<class T, class U> A(const typename B<T,U>::A&) {}; template<class T, class U> A& operator=(const typename B<T,U>::A&) }; - person Bjoern; 25.04.2011
comment
@Bjoern Конечно, ты прав (глупый я). Тогда я бы порекомендовал это ответ. - person Karl von Moor; 25.04.2011
comment
Если бы не было построений или присвоений объектов типа typename B<T1,U1>::A объектам типа typename B<T2,U2>::A (с другими параметрами шаблона), ваше решение было бы просто прекрасным! Фактически, я мог бы написать конструктор и оператор присваивания в общем виде. Так что спасибо, что поделились своими мыслями! - person Bjoern; 25.04.2011

Правильный способ сделать это в C ++ - это идиома PIMPL. Альтернативное решение - поместить класс, который вы хотите скрыть, во вложенное пространство имен, которое обычно называется detail. Но это не сделает его полностью приватным, поскольку пользователи по-прежнему будут подвержены его зависимостям и смогут использовать его напрямую.

person Community    schedule 25.04.2011

Документируйте, что этот класс не является частью общедоступного API и не должен использоваться.

В C ++ вам нужны доверенные программы, которые связываются с кодом вашей библиотеки, потому что у вас мало другого выбора. C ++ имеет ограниченные функции «контроля доступа», многие из которых можно обойти или злоупотребить, поэтому вам лучше относиться к своим клиентам API с уважением и укреплять доверие.

Если вы проектируете свой API так, чтобы его было легко использовать правильно, а сложно использовать непреднамеренно неправильно, вы будете помогать своим клиентам, и вряд ли ваша вина будет в том, что ваши клиенты злоупотребляют вашим интерфейсом.

person CB Bailey    schedule 25.04.2011

Безымянное пространство имен в любом случае бесполезно, поскольку оно защищает только несколько определений. Что вы могли бы сделать, так это использовать pImpl Idiom, как упоминалось в других ответах, или использовать пространство имен detail. Отлично работает для Boost:

namespace detail{
  class A{
    // ...
  };
}

class B{
public:
  // ...
private
  A a_;
};

Любой, кто возится с вещами в пространстве имен detail, напрашивается на проблемы. Или, может быть, скрыть это еще больше

namespace _b_impl_detail{
  // ...
};

Тот, кто сейчас дотронется до чего-нибудь внутри, должен быть ранен в ногу. :)

person Xeo    schedule 25.04.2011

Вместо class B, удерживающего объект A, пусть он удерживает A* (или shared_ptr<A>, или unique_ptr<A> и т. Д.). Таким образом, class B требуется только предварительное объявление class A, а class A может быть полностью определен внутри исходного файла class B.

person ildjarn    schedule 25.04.2011

Если A является деталью реализации B, вообще не помещайте его определение в заголовок. Вместо:

class B {

   ...
   class A * myA;
};

а затем поместите определение A в файл реализации B (то есть .cpp).

person Community    schedule 25.04.2011
comment
Извините, я действительно забыл упомянуть, что класс B на самом деле является шаблоном (в зависимости от многих параметров), и библиотека должна быть только с одним заголовком, без отдельного файла .cpp реализации. - person Bjoern; 25.04.2011

Я хотел бы добавить небольшое увеличение по сравнению с https://stackoverflow.com/a/5780976/1525238, которое помогло мне лучше решить мой особый случай использования, а именно, где «основной» класс является шаблоном, а «вспомогательный / внутренний» класс также должен быть шаблоном 1.

Я использовал вложенное пространство имен detail, сделал весь «вспомогательный» контент частным и сделал «основной» класс friend из «вспомогательного» класса:

template<__MAIN_TEMPLATE_PARAMS__> class Main;

namespace detail {
    template<__HELPER_TEMPLATE_PARAMS__> class Helper {

        /* All Main templates are friends */
        template<__MAIN_TEMPLATE_PARAMS__> friend class Main; 

        /* Private stuff, not reachable from the outside */
        static void privateThing(){
            ...
        }
    };
}

template<__MAIN_TEMPLATE_PARAMS__> class Main {
    void usePrivateThing(){
        detail::Helper<__DESIRED_HELPER_TEMPLATE_PARAMS__>::privateThing();
    }
};

Личные данные static выше только для того, чтобы сделать код короче. Они вполне могут быть привязаны к экземпляру Helper.

Оглядываясь назад, можно сказать, что, безусловно, могут быть более элегантные решения с меньшим количеством черной магии, но это сильно зависит от конкретного приложения. Я все еще считаю приведенное выше законным и приятным вариантом использования класса friend.


1 Это потому, что мне нужно было использовать вспомогательную функцию шаблона, которая требовала частичной специализации, которая пока не разрешена в C ++ без особой причины, но технически возможно с классом-оболочкой. Частичная специализация в приведенном выше описании для простоты опущена.

person Ayberk Özgür    schedule 23.12.2019