Разница между реализацией класса в файле .h или в файле .cpp - PullRequest
31 голосов
/ 27 ноября 2009

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

Чтобы лучше объяснить, о чем я говорю, я имею в виду различия между нормальным подходом:

// File class.h
class MyClass 
{
private:
  //attributes
  public:
  void method1(...);
  void method2(...);
  ...
};

//file class.cpp
#include "class.h"

void MyClass::method1(...) 
{
  //implementation
}

void MyClass::method2(...) 
{
  //implementation
}

и подход just-header :

// File class.h
class MyClass 
{
private:
  //attributes
public:
  void method1(...) 
  {
      //implementation
  }

  void method2(...) 
  {
    //implementation
  }

  ...
};

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

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

Ответы [ 6 ]

36 голосов
/ 27 ноября 2009

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

Если определения функций-членов находятся в собственных единицах перевода (файл .cpp), то они компилируются один раз, и только объявления функций компилируются несколько раз.

Это правда, что функции-члены, определенные (не только объявленные) в определении класса, неявно inline. Но inline не означает, что люди могут разумно догадаться, что это значит. inline говорит, что допустимо, чтобы несколько определений функции появлялись в разных единицах перевода и впоследствии были связаны друг с другом. Это необходимо, если класс находится в заголовочном файле, который будут использовать разные исходные файлы, поэтому язык пытается быть полезным.

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

Возможно, вы сможете лучше увидеть различия, если рассмотрите третий возможный способ сделать это:

// File class.h
class MyClass
{
    private:
        //attributes
    public:
       void method1(...);
       void method2(...);
       ...
};

inline void MyClass::method1(...)
{
     //implementation
}

inline void MyClass::method2(...)
{
     //implementation
}

Теперь, когда неявный inline исключен, остаются некоторые различия между этим подходом «весь заголовок» и подходом «заголовок плюс источник». То, как вы делите свой код между единицами перевода, имеет последствия для того, что происходит при его создании.

7 голосов
/ 27 ноября 2009

Да, компилятор попытается встроить метод, объявленный непосредственно в заголовочном файле, например:

class A
{
 public:
   void method()
   {
   }
};

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

  1. У вас не будет раздувания кода из-за того, что один и тот же код включается в несколько блоков перевода
  2. Ваше время компиляции сократится коренным образом. Помните, что для любого модификация в заголовочном файле компилятор должен строить все остальное файлы, которые прямо или косвенно включи это. Я думаю, это будет очень разочарование для любого, чтобы построить весь двоичный файл снова только для добавления пробел в заголовочном файле.
7 голосов
/ 27 ноября 2009

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

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

Как уже указывали некоторые другие ответы, да, определение метода в блоке class файла приведет к включению компилятора.

2 голосов
/ 27 ноября 2009

Да, определение методов внутри определения класса эквивалентно их объявлению inline. Там нет другой разницы. Нет смысла определять все в заголовочном файле.

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

1 голос
/ 03 января 2010

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

0 голосов
/ 03 января 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...