шаблоны композиции в С++, есть ли предпочтительный способ?

Хорошо, позвольте мне начать с того, что это, вероятно, очень субъективно и спорно, и, вероятно, здесь не место (не стесняйтесь закрывать, если чувство взаимно). Сказав это, я смотрю на некоторый код и хочу прийти к стандартному подходу к композиции, поскольку кажется, что у разных людей разные стили - так что вот стили, которые я видел до сих пор (может быть больше ..) Конкретная проблема композиции, которую я рассматриваю, заключается в том, что класс B владеет экземпляром класса A, но A должен знать этот экземпляр, чтобы он мог вызывать методы B.

#include <iostream>

using namespace std;

namespace forward
{
  class B;

  class A
  {
  public:
    A(B& b); 
  private:
    B& inst;
  };

  class B
  {
  public:
    B() : inst(*this) {}

    void foo() { cout << "forward::B::foo()" << endl; }

  private:
    A inst;
  };

  A::A(B& b) : inst(b) { inst.foo(); }
}

namespace interface
{
  struct IB
  {
    virtual void foo() = 0;
  };

  class A
  {
  public:
    A(IB* b) : inst(b) { inst->foo(); }
  private:
    IB* inst;
  };

  class B : public IB
  {
  public:
    B() : inst(this) {}

    virtual void foo() { cout << "interface::B::foo()" << endl; }

  private:
    A inst;
  };
}

namespace templated
{
  template <typename IB>
  class A
  {
  public:
    A(IB& b) : inst(b) { inst.foo(); }
  private:
    IB& inst;
  };

  class B
  {
  public:
    B() : inst(*this) {}

    void foo() { cout << "templated::B::foo()" << endl; }

  private:
    A<B> inst;
  };   
}

int main(void)
{
  forward::B b1;    
  interface::B b2;    
  templated::B b3;    

  return 0;
}

Из этого я вижу следующее (не полное):

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

Интерфейсы Дополнительный багаж (конструкции базового класса, вызовы виртуальных функций и т. д.)

template Я не вижу никаких проблем с этим, кроме проблем с компиляцией (например, нелепые сообщения об ошибках и т. д.)

Теперь я предпочитаю шаблонный подход - я думаю, что он чистый и имеет то преимущество, что время компиляции принудительно. Итак, суть вопроса в том, есть ли что-то технически неправильное в этом подходе, и если нет, то почему вы должны использовать два других подхода?


РЕДАКТИРОВАТЬ: я думаю, что тривиальный пример не помог, в этом конкретном случае B является менеджером ресурсов и владеет различными компонентами, которые взаимосвязаны (A) - например, различные сетевые подключения и т. д. Все подкомпоненты могут получать доступ друг к другу через B - вся система раньше была набором синглетонов... Таким образом, единственная причина, по которой A знает о B, заключается в том, что он предоставляет доступ к какому-то другому компоненту, который нужен A...

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


person Nim    schedule 21.01.2011    source источник
comment
Мне кажется, что каждый из этих стилей решает разные проблемы, в зависимости от того, что требуется от дизайна.   -  person James    schedule 21.01.2011


Ответы (6)


Рассматривали ли вы решение основной проблемы дизайна, чтобы у вас не было члена, который должен знать своего родителя, чтобы функционировать? Без дополнительной информации о причинах такого расположения трудно порекомендовать лучшую альтернативу, но основная проблема, по-видимому, заключается в дизайне, а не в технике.

ИЗМЕНИТЬ Похоже, вы ищете локатор сервисов. Посмотрите на это и посмотрите, не поможет ли это решить ваши проблемы с дизайном.

person Harper Shelby    schedule 21.01.2011
comment
Я пытаюсь уйти от статических синглетонов... В этом смысле мне не нужен одноэлементный реестр, потому что объект, которому принадлежат ресурсы, передается в каждый экземпляр ресурсов, которыми он владеет, поэтому все ресурсы могут найти друг друга через это (сродни локатору сервисов, без противного синглтона) - и ИМХО, гораздо проще проводить модульное тестирование! - person Nim; 21.02.2011
comment
Хотя я не разделяю распространенного мнения о том, что слово «одиночка» — ругательство, я могу понять вашу позицию. Если вы не будете менять дизайн, то прямое объявление — это действительно идиоматический способ C++ для решения этой проблемы. Что касается «ограничения» невозможности использовать тип в заголовке, это нормально — вы должны иметь только ссылки или указатели в заголовке и включать необходимый заголовок в файл реализации. - person Harper Shelby; 22.02.2011

Я бы выбрал первое решение, оно делает именно то, что вы хотите.

Я бы рассмотрел два других решения, обходные пути, поскольку:

  • интерфейсы здесь не нужны и, как вы сказали, добавляют сложности и накладные расходы.

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


был бы даже четвертый способ облегчить эту проблему: облегчение declare перед использованием< /em> правило

person peoro    schedule 21.01.2011
comment
Интересно, я всегда думал, что форвардинг — это хак, для которого шаблоны — чистое решение! ;) - person Nim; 24.01.2011

Если A и B объявлены в одном и том же заголовочном файле, «прямое» решение является наиболее простым IMO. В противном случае используйте «интерфейсное» решение.

person Frederik Slijkerman    schedule 21.01.2011
comment
Их нет даже в одних и тех же библиотеках! B на самом деле находится в фреймворке, который контролируется набором признаков и классов политик, и существует множество продуктов, которые используют этот фреймворк, каждый со своими собственными характеристиками и политиками, но B владеет ресурсами A. Я предполагаю, что это проблема с попыткой решить проблему на простом примере... - person Nim; 24.01.2011

Это не композиция, это просто наследование, то есть B эффективно наследует от A, и мне просто кажется, что вам нужно перепроектировать свои классы.

Я лично использую решение для пересылки, но, конечно, на моих занятиях B не содержит A.

person Puppy    schedule 21.01.2011
comment
Это не наследование, опять же, я попытался сформулировать простой пример и это, вероятно, запутало вопрос, как я сказал в правке, B действительно владеет A и нет смысла наследовать от него (я не большой в любом случае поклонник наследования - это слишком злоупотребляет) - person Nim; 24.01.2011

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

Я бы рассмотрел форвардные объявления, если принадлежащий класс является (или функционально может быть) вложенным классом. Что еще A назвал бы тогда B?

interface будет использоваться, когда разумно предположить, что используются разные обратные вызовы, но только 1 для каждого класса (что менее вероятно).

В противном случае я бы использовал сигнальный механизм в качестве обратного вызова (наблюдатель)

Откровенно говоря, я не понимаю, что вам может предложить template, возможно, потому, что я еще не использовал его.

person stefaanv    schedule 21.01.2011
comment
шаблоны - это ИМХО элегантное решение взлома, которое представляет собой предварительные объявления. Сообщения об ошибках пугают, но как только вы привыкнете к причудам вашего компилятора, для декодирования не потребуется много усилий (опять же просто мнение). . - person Nim; 24.01.2011

Подобные циклические зависимости часто указывают на то, что дизайн необходимо пересмотреть. Часто лучшим решением является введение третьего класса C, который является посредником/взаимодействует между A и B (или с которым взаимодействуют A и B).

Однако, если A и B действительно очень тесно связаны, используйте предварительные объявления, как в первом примере, и поместите оба класса в один и тот же заголовок.

РЕДАКТИРОВАТЬ (для редактирования OP): в этом случае это звучит так, как будто вам нужен класс «алгоритм», который знает о менеджере И всех подобъектах и ​​делегирует каждую часть работы соответствующему содержащемуся объекту (получая его из контейнера по мере необходимости). Например, он может передать ссылку на B в методы A, которые в ней нуждаются, вместо того, чтобы вводить циклическую зависимость.

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

person Mark B    schedule 21.01.2011
comment
B владеет только A (и другими, подобными A), мой тривиальный пример не помог понять это, подумайте о B как об управляющем ресурсами, где A — это ресурсы, однако A для работы требуется доступ к другим ресурсам (принадлежащим B), т.е. B это то же самое, что и ваше C! (ааа, слишком много ABC для утра понедельника! :() - person Nim; 24.01.2011