Какие шаблоны вы используете для разделения интерфейсов и реализации в C ++? - PullRequest
8 голосов
/ 10 июня 2009

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

Что вы делаете в этой ситуации?

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

Ответы [ 6 ]

9 голосов
/ 10 июня 2009

Образец прыщей:

В вашем заголовочном файле объявляйте только открытые методы и закрытый указатель (указатель pimpl или делегат) на заранее объявленный класс реализации.

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

Plus:

  • Позволяет изменить реализацию вашего класса без необходимости перекомпилировать все.
  • Наследование работает хорошо, только синтаксис становится немного другим.

Минус:

  • Много и много глупых тел методов, которые нужно написать для делегирования.
  • Вроде бы неудобно отлаживать, так как вам нужно пройти через тонны делегатов.
  • Дополнительный указатель в вашем базовом классе, который может быть проблемой, если у вас много маленьких объектов.
6 голосов
/ 10 июня 2009

John Lakos ' Large Scale C ++ Design * - превосходная книга, посвященная проблемам, связанным с созданием больших проектов C ++. Все проблемы и решения основаны на реальности, и, конечно, вышеупомянутая проблема обсуждается достаточно подробно. Настоятельно рекомендуется.

2 голосов
/ 10 июня 2009

Использование наследования:

В вашем заголовке объявите открытые методы как чисто виртуальные методы и фабрику.

В вашем источнике извлекайте класс реализации из вашего интерфейса и реализуйте его. В реализации фабрики возвращают экземпляр реализации.

Plus:

  • Позволяет изменить реализацию вашего класса без необходимости перекомпилировать все.
  • Легко и надежно реализовать.

Минус:

  • Действительно неловко определять (публичный) производный экземпляр публичного базового класса, который должен наследовать некоторые методы (частной) реализации публичной базы.
0 голосов
/ 10 июня 2009

None.

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

  1. Ясность на первом месте. Если компромисс ясность для времени выполнения скорость нужно учитывать дважды, что о компромиссной ясности для время компиляции скорость?
  2. Частные участники не должны меняться так часто.
  3. Как правило, это занимает много времени, чтобы восстановить все.
  4. Более быстрые инструменты появятся в будущем, поэтому проблема со скоростью компиляции будет автоматически уменьшена. Ваш код не станет автоматически понятнее.
  5. Вам все равно придется часто перестраивать.
  6. Вы пробовали Incredibuild ?

Конечно, в конце концов, это экономическое решение. Если вес «3» важен в вашем проекте и по какой-то причине «6» не может быть применен, тогда действуйте: вы выиграете больше от использования этих шаблонов, чем потеряете.

0 голосов
/ 10 июня 2009

Рефакторинг и использование языка pimpl / handle-body, использование чисто виртуальных интерфейсов, чтобы скрыть детали реализации, кажется популярным ответом. При проектировании больших систем следует учитывать время компиляции и производительность разработчика. Но что, если вы работаете на существующей большой системе C ++ без охвата модульных тестов? О рефакторинге обычно не может быть и речи.

Что я обычно делаю, когда не хочу, чтобы компилятор компилировал мир после того, как я коснулся некоторых распространенных заголовочных файлов, - это наличие make-файла / скрипта для компиляции только тех файлов, которые, как я знаю, нуждаются в перекомпиляции. Например, если я добавляю к классу не виртуальную частную функцию, необходимо перекомпилировать только файл cpp класса, даже если его заголовочный файл включен в сотню других файлов. Прежде чем уйти на день, я начинаю чистую сборку, чтобы восстановить мир.

0 голосов
/ 10 июня 2009

Вы можете использовать прямое объявление для класса A, на которое ссылается указатель в другом классе B. Затем вы можете включить файл заголовка класса A в файл реализации класса B, а не в файл заголовка. Таким образом, изменения, внесенные в класс A, не затронут исходные файлы, которые включают заголовочный файл класса B. Любой класс, который хочет получить доступ к членам класса А. должен будет включать заголовочный файл класса А.

...