Подтвердить что ты не робот

Разница между реализацией класса внутри файла .h или в файле .cpp

Мне было интересно узнать, какие различия между объявлением и реализацией класса исключительно в файле заголовка, по сравнению с обычным подходом, в котором вы протиперуете класс в заголовке и реализуете эффективный файл .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
}

и подход с просто заголовком:

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

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

  ...
};

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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