Идиома C ++ без исходного кода

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

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

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};

#ifdef LIBFOO_COMPILE_INLINE
void Bar::CircularDependency(void)
{
  //...
}
#endif

Тогда проект, использующий libfoo, сделает в main.cpp следующее:

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

И в любом другом .cpp:

//other.cpp
#include "libfoo.h"

Дело в том, что секция compile-inline компилируется только один раз (в main.cpp).

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

Небольшое отступление: я знаю, что многие программисты, не без оснований, предпочитают, чтобы их заголовки напоминали интерфейсы, а не реализации, но генераторы документации IMHO лучше подходят для описания интерфейсов, потому что мне нравится скрывать частные члены все вместе: -)


person William Andrew Burnson    schedule 23.03.2012    source источник
comment
Почему бы просто не поместить определение в файл Cpp, как кто-либо другой, вместо того, чтобы проделать какой-нибудь трюк, подверженный ошибкам?   -  person Mooing Duck    schedule 23.03.2012
comment
Мне это кажется не очень естественным. Если вы хотите написать Java, используйте Java. Импорт их стиля в C ++ кажется, что вы просто хотите продолжать делать это так, как вы выучили свой исходный язык. На SO есть много статей об обучении написанию кода в стиле, соответствующем языку. programmers.stackexchange.com/q/112162 / 12917   -  person Martin York    schedule 23.03.2012
comment
@LokiAstari - на самом деле этот вид уловки (попытка поместить реализацию в заголовок) восходит к тому времени, когда была изобретена Java (или, черт возьми, даже C ++!). Я видел код, объявляющий переменные в заголовках, используя аналогичный трюк, но с добавлением макроса сверху для удобства.   -  person Michael Kohne    schedule 23.03.2012
comment
‹Sarcasm› Если к этому приводит необходимость предоставления шаблонных реализаций в заголовках, это, вероятно, приводит только к одному файлу заголовка. Вы обдумывали это? ‹/Sarcasm› [По крайней мере, с помощью одного файла заголовка вы можете контролировать порядок определений и избежать бремени определения макросов для людей, которые включают вашу библиотеку]   -  person David Rodríguez - dribeas    schedule 23.03.2012
comment
@ DavidRodríguez-dribeas Собственно то, что я делаю ;-) Я собираю всю библиотеку в один заголовок. (Лучше или хуже.)   -  person William Andrew Burnson    schedule 25.03.2012
comment
Если вы не понимали теги ‹sarcasm›, позвольте мне сказать вам, переосмыслите свой дизайн, не каждая библиотека предназначена только для заголовка, если вам нужно предоставить скомпилированный двоичный файл (или исходные коды для компиляции пользователем) Не каждая библиотека в стандарте используется только заголовок, не каждая библиотека в boost является только заголовком, и не думайте, что вы можете перехитрить людей, которые пишут язык, или людей в boost.   -  person David Rodríguez - dribeas    schedule 25.03.2012


Ответы (3)


Вы должны иметь возможность использовать ключевое слово inline, если проблема циклической зависимости будет решена к тому времени, когда определение Bar::CircularDependency() появится в заголовке libfoo.h:

//part of libfoo.h
class Bar
{
  void CircularDependency(void);
};


// other stuff that 'resolves' the circular dependency
// ...
// ...

inline void Bar::CircularDependency(void)
{
  //...
}

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

person Michael Burr    schedule 23.03.2012
comment
Вау, я бы подумал, что это приведет к дублированию символов, но это не так - это действительно здорово. Я предполагаю, что компилятор все еще воспринимает это как указание на то, чтобы фактически встроить метод? - person William Andrew Burnson; 25.03.2012
comment
Один из способов взглянуть на ключевое слово inline - сказать компилятору, что «одно правило определения» не применяется к этой функции. И да, компилятор обычно пытается встроить функцию, но это не обязательно. - person Michael Burr; 25.03.2012
comment
Я полагаю, что тот же эффект может быть достигнут (без свойства inling) за счет злоупотребления шаблонами? т.е. создать частный template <class Dummy> void Bar::CircularDependency_impl(void) и вызвать его CircularDependency (). Затем шаблонный метод обходит ODR, но может быть реализован в любое удобное время. - person William Andrew Burnson; 25.03.2012

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

person Dietmar Kühl    schedule 23.03.2012

Мои причины, почему это плохая идея.

Увеличение времени компиляции

Каждая отдельная единица компиляции, которая включает заголовок, должна компилировать все файлы заголовков в дополнение к самому источнику. Это, вероятно, увеличит время компиляции и может разочаровать при тестировании вашего кода с небольшими изменениями. Можно было бы утверждать, что компилятор может оптимизировать его, но ИМО он не мог оптимизировать его сверх определенной точки.

Большой сегмент кода

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

Создает зависимость в клиентском коде с сильной связью

Всякий раз, когда вы меняете свою реализацию, каждый клиент должен обновляться (путем повторной компиляции кода). Но если реализация была помещена в независимый общий объект (.so или .dll), клиент должен просто связать его с новым общим объектом.

Также я не уверен, зачем это нужно делать.

//main.cpp
#define LIBFOO_COMPILE_INLINE
#include "libfoo.h"

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

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

person Senthil Babu    schedule 23.03.2012
comment
Директива inline не означает, что компилятор должен поместить весь этот код везде, где вызывается функция. Это просто подсказка для компилятора. То, действительно ли он встроен, зависит от настроек компилятора (без включенных оптимизаций, как правило, ничего не встроено, а оптимизация по размеру приведет к меньшему встраиванию, за исключением тривиальных функций) и от конкретного сайта вызова. - person Jens Alfke; 19.09.2016