2009-11-27 1 views
28

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

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

+0

Если у вас есть предохраняющие препроцессоры, он не будет создавать больше экземпляров одной и той же реализации. –

+0

Вы говорите о #ifndef _CLASS_H_ #define _CLASS_H_ и т.д ..? – Jack

+9

@Andrew: обязательно это будет, если два.В файлах cpp оба включают один и тот же заголовок, после чего каждый будет компилировать его содержимое в полном объеме, независимо от каких-либо препроцессорных охранников, и что скомпилированный контент будет присутствовать в выходном объектном файле. Компилятор удаляется только компоновщиком. Защитные устройства препроцессора должны грациозно обрабатывать двойное включение заголовка одной единицей перевода, а не разделять между единицами перевода. –

ответ

33

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

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

+0

infact Я говорил о «запросе компилятору», я знаю, что это необязательно эффективная инкрустация. – Jack

+0

Да, я повторял ее на всякий случай, если бы какой-либо читатель с новым кодом на C++ забыл к тому моменту, когда они пробрались через мой первые три абзаца. –

7

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

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

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

1

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

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

+1

Почему это проголосовало? Для меня это выглядит правильно (и информативным) .... –

+3

Троллинг по-видимому. Нисходящий опрос без объяснения - это всегда троллинг. – AnT

+1

Очевидным моментом является то, что он короче. Это не очень хорошая причина для публичных заголовков, учитывая недостатки. Но для небольшого класса утилиты, используемого внутри одного модуля, все отлично. –

7

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

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

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

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

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

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

1

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

Смежные вопросы