Определение конструктора в заголовочном файле против файла реализации (.cpp) - PullRequest
29 голосов
/ 21 января 2011

Я могу определить тело конструктора класса в файле класса .h или в файле реализации .cpp .Эти два стиля, вероятно, идентичны в том, что касается компилятора в конкретном проекте (проект для меня означает DLL ).То же самое относится и к любым функциям-членам: они могут быть определены в заголовочном файле или просто объявлены там, а затем определены в файле cpp.

Однако я обнаружил, что если мне нужно включить такие файлы заголовков классов в разные проекты (это означает, что в конечном итоге код, использующий файл заголовков, заканчивается в другой DLL )тогда фактическая реализация в заголовочном файле вызывает некоторые головные боли при компиляции (не при компоновке ... я даже не дохожу до этого момента).Зачем?Что ж, я не буду вдаваться в подробности, но компилятор, очевидно, пытается разрешить все функции, которые могут быть определены в других заголовочных файлах и т. Д., Вынуждая плохого разработчика начинать добавлять различные заголовочные файлы и т. Д.

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

Что вы думаете об этом?

Ответы [ 3 ]

27 голосов
/ 21 января 2011

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

Я не вижу причин делать исключения для конструкторов. Поместите их в файл .cpp.

25 голосов
/ 21 января 2011

Следует отметить один важный момент: если функция-член определена в заголовочном файле, она должна быть либо внутри тела класса, либо явно помечена как inline.Другими словами, это просто неправильно делать это в заголовочном файле:

class A {
  public:
    A();
};

A::A() {
  // constructor body
}

Причина в том, что это неправильно, потому что он заставит компилятор включать определение в каждую единицу компиляции, в то время как очевидно, что любая функциядолжны быть определены только один раз.Вот правильные способы сделать то же самое:

class A {
  public:
    inline A();
};

inline A::A() {
  // constructor body
}

Или:

class A {
  public:
    inline A() { // inline isn't required here, but it's a good style
     // constructor body
    }
};

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

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

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

1 голос
/ 21 января 2011

Еще одно замечание: любые изменения в заголовочном файле требуют пересоздания всех файлов, которые содержат этот заголовочный файл.Большинство систем сборки перестраивают исходные (* .cpp / .cc) файлы, которые зависят от измененного заголовочного файла.

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

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

...