Идиома Pimpl и совместная работа с внутренними объектами без объявления друга

Я реализую несколько классов, используя идиому pimpl, и сталкиваюсь с некоторыми проблемами дизайна.

Во-первых, я всегда видел, как pimpl делают вот так

class Object
{
public:
    Visible();
    ~Visible();
 .. etc ..
private:
    class ObjectImpl *_pimpl;
};

У меня есть несколько классов, которые используют этот подход, и моя проблема в том, что некоторым из этих классов требуется доступ к деталям реализации друг друга, но указатель _pimpl является закрытым.

Может ли кто-нибудь увидеть обратную сторону объявления _pimpl общедоступным. Очевидно, что если он публичный, то кто-то может случайно (или намеренно) переназначить его. (Я игнорирую тот факт, что "частный" может быть # определен как "общедоступный" и в любом случае предоставить доступ. Если вы сделаете это, то вы заслуживаете того, что получаете).

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

Мне очень не нравится использовать друзей, и я не уверен, что они даже помогут, поскольку вы не можете пересылать объявление Object :: ObjectImpl без полного определения Object.

i.e.

 ...
 private:
    class ObjectImpl *_pimpl;
    friend class OtherObject::OtherObjectImpl; // this needs a fully qualified OtherObject
};

Спасибо, Марк.

* ОБНОВЛЕНИЕ - Подробнее **

У меня есть два класса: один называется Command, а другой - Results. У меня есть методы Command, которые возвращают вектор результатов.

И команда, и результаты используют идиому pimpl. Я хочу, чтобы интерфейс с результатами был как можно меньше.

class Command
{
public:
    void getResults( std::vector< Results > & results );
    void prepareResults( std::vector< Results > & results );
private:
    class CommandImpl *_pimpl;
};

class Results
{
public:
    class ResultsImpl;

    Results( ResultsImpl * pimpl ) :
        _pimpl( impl )
    {
    }

private
    ResultsImpl *_pimpl;
};

Теперь в Command :: getResults (). Я добавляю ResultsImpl в результаты. в Command :: prepareResults () мне нужен доступ к ResultsImpl.

M.


person ScaryAardvark    schedule 24.06.2011    source источник
comment
Думаю, нужно немного подробнее.   -  person Cheers and hth. - Alf    schedule 24.06.2011


Ответы (4)


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

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

  int GetImplementationDetail();

private:
  std::unique_ptr< ObjectImpl > _pimpl;
};

int Object::GetImplementationDetail()
{
  return pimpl->GetImplementationDetail();
}

Кроме того, класс должен отвечать за одно, только за одно и должен иметь минимум зависимостей от других классов; если вы думаете, что другие классы должны иметь доступ к pimpl вашего объекта, то ваш дизайн, скорее всего, ошибочен.

изменить после обновления автора: хотя ваш пример все еще довольно расплывчатый (или, по крайней мере, я не могу сказать его полное намерение), похоже, вы неправильно истолковываете идиому и теперь пытаетесь применить ее к случаю где это бесполезно. Как отмечали другие, «P» означает частный. Ваш класс результатов не имеет особой частной реализации, поскольку все это общедоступно. Так что либо попробуйте использовать то, что я упомянул выше, и ничего не «вводите», либо просто избавьтесь от pimpl и используйте только класс Result. Если интерфейс вашего класса Result должен быть настолько маленьким, что это не что иное, как указатель на другой класс, в этом случае он, похоже, не имеет особого смысла.

person stijn    schedule 24.06.2011
comment
+1: приятное прикосновение к unique_ptr :) Обратите внимание, что может быть предоставлен настраиваемый удалитель (вместо определения деструктора), даже если чистый эффект будет очень похожим. - person Matthieu M.; 24.06.2011
comment
@sbi: автоматическая обработка памяти с правильным деструктором и перемещением конструктора / присваивания бесплатно - person Matthieu M.; 24.06.2011
comment
@sbi: shared_ptr часто (плохо) используется для Pimpl, без должного учета общей семантики, которую он подразумевает: / - person Matthieu M.; 24.06.2011
comment
@Matthieu: Я не уверен, что неявная семантика перемещения намного лучше, чем неявная семантика совместного использования. Что мне не хватает? - person sbi; 24.06.2011
comment
@sbi: когда объект перемещается, все еще остается уникальный владелец (как следует из имени unique_ptr), поэтому мы все еще говорим об объектах значений и только оптимизируем их передачу. С другой стороны, совместное владение означает, что несколько ссылочных объектов совместно используют одни и те же базовые данные, и, таким образом, изменение одного из них отражается на других. - person Matthieu M.; 24.06.2011
comment
Привет, stijn, мне жаль, что это немного затянулось. У самого класса Results есть еще несколько общедоступных методов, но большинство из них будут частными, и поскольку я хотел избежать зависимости от времени компиляции, а также хотел сохранить частную реализацию, я выбрал pimpl. ResultsImpl - это то место, где находится реальная сторона, однако у меня есть другие классы, которые должны будут использовать часть ResultsImpl в результатах, и я не хочу раскрывать это в общедоступном интерфейсе результатов. Отсюда моя проблема. Возможно, у меня действительно есть недостаток в дизайне, и я хочу, чтобы Impls перекрестно общались. - person ScaryAardvark; 27.06.2011
comment
это по-прежнему означает, что это не совсем pimpl, поскольку реализация видна снаружи. Вы можете сделать что-то вроде этого: записать getResults как getresults (std :: vector ‹Result› & results, std :: vector ‹std :: shared_ptr ‹ResultImpl››). Затем в getResults вы создаете кучу sherd_ptr и вставляете их в результаты, а также вставляете их в вектор. Затем другие классы могут получить доступ к нужному им impl из вектора, в то время как сам Result также имеет ссылку на тот же impl через его shared_ptr. Хотя это все еще немного беспорядочно. - person stijn; 27.06.2011

Почему именно ваши классы зависят от деталей друг друга? Вы можете переосмыслить свой дизайн. Классы должны зависеть от абстракций.

Если вы действительно считаете свой дизайн правильным, ничто не мешает вам предоставить частный заголовок исходного файла, например:

include/
   foo.h
src/
   foo.impl.h
   foo.c
   bar.c

а затем #include foo.impl.h как в foo.c, так и в bar.c

foo.c:
    #include "foo.impl.h"
    ...

bar.c:
    #include "foo.impl.h"
    ...

Но опять же: как правило,

Принцип инверсии зависимостей:

A. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.

Б. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Также обязательно ознакомьтесь с SOLID и GRASP, особенно о слабой связи.

person Sebastian Mach    schedule 24.06.2011
comment
Вы предлагаете, чтобы у меня был ResultsImplInterface, который я мог бы затем вернуть в общедоступном методе получения результатов. Таким образом, все еще сохраняя фактическую реализацию в секрете ?. - person ScaryAardvark; 27.06.2011
comment
Если вы удерживаете его в указателе или ссылке, просто чтобы позже перенаправить его другому классу, тогда это будет применимо. - person Sebastian Mach; 27.06.2011
comment
На самом деле, зависимость классов от интерфейсов не решит мою непосредственную проблему. Это не избавит меня от необходимости получить доступ к частному члену pimpl классов Results. Превращение объекта impl в интерфейс не меняет его защиты. - person ScaryAardvark; 27.06.2011

Таким образом, нет необходимости квалифицировать друга.

Если вы просто используете friend class OtherObject, тогда класс OtherObject может получить доступ к необходимым внутренним компонентам.

Лично мой Pimpl - это просто struct (пакет данных), и я оставляю методы для работы с ним в исходном классе.

person Matthieu M.    schedule 24.06.2011

Создание члена данных _pimple public ничего вам не даст, пока другие классы не видят определение его типа ObjectImpl - как раз то, что изложил Pimple. чтобы предотвратить.

Вместо этого вы можете добавить частный интерфейс к вашему Object классу, который позволит дружественным классам делать все, что им нужно, с Object.

Конечно, все распространенные оговорки о том, что friend является инструментом, который следует использовать как можно реже, применимы.

person sbi    schedule 24.06.2011