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

StackOverflow https://stackoverflow.com/questions/1809679

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top