(избегайте) разделения кода на.cpp и.h в C++ и эффективной компиляции
Обычная практика в C++ - разделять объявления в .h
(или же .hpp
) и внедрение в .cpp
,
Я знаю о двух основных причинах (возможно, есть и другие):
- Скорость компиляции (вам не нужно перекомпилировать все, когда вы изменяете только один файл, вы можете связать его
make
из предварительно скомпилированного.o
файлы) - Предварительные декларации иногда необходимы (при реализации
class A
зависит отclass B
и реализацияclass B
наclass A
) ... но у меня нет этой проблемы так часто и обычно я могу ее решить.
В случае объектно-ориентированного программирования это выглядит так:
QuadraticFunction.h
:
class QuadraticFunc{
public:
double a,b,c;
double eval ( double x );
double solve( double y, double &x1, double &x2 );
};
QuadraticFunction.cpp
:
#include <math.h>
#include "QuadraticFunc.h"
double QuadraticFunc::eval ( double x ){ return c + x * (b + x * a ); };
double QuadraticFunc::solve( double y, double &x1, double &x2 ){
double c_ = c - y;
double D2 = b * b - 4 * a * c_;
if( D2 > 0 ){
double D = sqrt( D2 );
double frac = 0.5/a;
x1 = (-b-D)*frac;
x2 = (-b+D)*frac;
}else{ x1 = NAN; x2 = NAN; }
};
main.cpp
:
#include <math.h>
#include <stdio.h>
#include "QuadraticFunc.h"
QuadraticFunc * myFunc;
int main( int argc, char* args[] ){
myFunc = new QuadraticFunc();
myFunc->a = 1.0d; myFunc->b = -1.0d; myFunc->c = -1.0d;
double x1,x2;
myFunc->solve( 10.0d, x1, x2 );
printf( "soulution %20.10f %20.10f \n", x1, x2 );
double y1,y2;
y1 = myFunc->eval( x1 );
y2 = myFunc->eval( x2 );
printf( "check %20.10f %20.10f \n", y1, y2 );
delete myFunc;
}
затем скомпилируйте его с makefile
как это:
FLAGS = -std=c++11 -Og -g -w
SRCS = QuadraticFunc.cpp main.cpp
OBJS = $(subst .cpp,.o,$(SRCS))
all: $(OBJS)
g++ $(OBJS) $(LFLAGS) -o program.x
main.o: main.cpp QuadraticFunc.h
g++ $(LFLAGS) -c main.cpp
QuadraticFunc.o: QuadraticFunc.cpp QuadraticFunc.h
g++ $(LFLAGS) -c QuadraticFunc.cpp
clean:
rm -f *.o *.x
Тем не менее, я нахожу это часто очень неудобным
особенно, когда вы сильно меняете код (например, на начальном этапе разработки, когда вы еще не уверены в общей структуре всего проекта).
- Вы должны идти вперед и назад все время между
.cpp
а также.h
часть кода при внесении значительных изменений в структуру класса. - У вас в два раза больше файлов в редакторе и в папке проекта, что сбивает с толку.
- Вы должны написать некоторую информацию (например, заголовки функций или
QuadraticFunc::
) дважды, где вы можете сделать много опечаток и несоответствий, так что компилятор постоянно жалуется (я делаю такие ошибки очень часто) - Каждый раз, когда вы добавляете / удаляете / переименовываете какой-то класс, вы должны редактировать
Makefile
где вы делаете много других ошибок, которые трудно отследить по выходным данным компилятора (например, я часто забывал написать Makefile, чтобы код перекомпилировал каждую зависимость, которую я редактирую)
С этой точки зрения мне больше нравится, как работает Java. По этой причине я писал свои программы на C++, просто помещая весь код (включая реализацию) внутрь .h
, Как это:
#include <math.h>
class QuadraticFunc{
public:
double a,b,c;
double eval ( double x ){ return c + x * (b + x * a ); }
double solve( double y, double &x1, double &x2 ){
double c_ = c - y;
double D2 = b * b - 4 * a * c_;
if( D2 > 0 ){
double D = sqrt( D2 );
double frac = 0.5/a;
x1 = (-b-D)*frac;
x2 = (-b+D)*frac;
}else{ x1 = NAN; x2 = NAN; }
};
};
с универсальным стандартным make-файлом, например:
FLAGS = -std=c++11 -Og -g -w
all : $(OBJS)
g++ main.cpp $(LFLAGS) -w -o program.x
(main.cpp
остается такой же)
Однако теперь, когда я начинаю писать более сложные программы, время компиляции начинает быть довольно долгим, когда мне приходится все время перекомпилировать.
Есть ли способ, как использовать преимуществаmake
(более быстрое время компиляции) и по-прежнему организовывать структуру программы в стиле Java (все в теле класса вместо отдельного .h
а также .cpp
) что мне гораздо удобнее?
4 ответа
Однако теперь, когда я начинаю писать более сложные программы, время компиляции начинает быть довольно долгим, когда мне приходится все время перекомпилировать.
Одним из лучших моментов в разделении заголовка и файла класса является то, что вам не нужно все компилировать.
Если у вас есть class1.h, class1.cpp, class2.h, class2.cpp, ...,classN.h и classN.cpp, эти заголовки включаются только в скомпилированные объекты каждого класса. Поэтому, если логика вашей функции изменяется в class2, а ваш заголовок - нет, вам нужно только скомпилировать class2 в объектный файл. Затем вы создадите ссылки для всех объектных файлов, которые создают ваш фактический исполняемый файл. Связывание БЫСТРО.
Если вы создаете большие и сложные программы и считаете, что редактирование заголовков является проблемой, подумайте о ДИЗАЙНЕ вашего приложения перед его написанием.
C++ - это C++, а Java - это Java. Разделение вашего исходного кода на файлы.h- и.cpp является частью языковой концепции C++. Если вам не нравится это, вы не должны использовать это.
Помещение всего в один заголовочный файл практически то же самое, что и включение файла.cpp (что работает, но крайне неуместно). Вы не должны делать это, когда:
- написание стандартных классов, функций,...
- многократное использование раздела кода в больших программах (приводит к ошибке переопределения, когда вы все включаете в main.cpp в противном случае)
- Вы хотите передать часть вашей программы в статическую / динамическую библиотеку. Практически каждая созданная доступная библиотека работает таким образом.
Примеры: WindowsAPI (COM!!), SFML, Boost (частично) (и многое другое)
Вы можете сделать это, когда:
- код выполняет очень простые вещи, например, операция сдвига битов (создание цветовых кодов), анализ строк,...
Примеры: Повышение (частично)
Вы должны сделать это, когда:
- Создание шаблонных классов или функций по мере их создания во время компиляции. Это один из основных и наиболее обсуждаемых недостатков концепции.h /.ccp, так что вы не первый, кто задумывается об этом.
Примеры: STL (стандартная библиотека шаблонов C++)
Краткий ответ: нет.
Длинный ответ: до сих пор нет.
Либо вы помещаете весь свой код в файл заголовка, либо используете два файла, где заголовок включен, а исходный файл скомпилирован самостоятельно.
У меня лично нет проблем с использованием двух файлов. Большинство редакторов поддерживают "просмотр двух файлов" - и большинство из них также поддерживают "переход к определению".
Помещение всех функций в объявление класса также имеет другой побочный эффект, заключающийся в том, что все функции помечены как встроенные, что может привести к многократному созданию функции в выходных двоичных файлах, если один и тот же заголовок включен в несколько исходных файлов.
Хотя время компиляции, по моему опыту, является не следствием синтаксического анализа, а частью компиляции кода (обычно это файл.cpp), поэтому, если вы включите несколько больших заголовков, скорее всего, это не будет иметь большого значения,
Конечно использовать make
[или что-то подобное] с правильно определенными зависимостями для построения вашего проекта.
Прежде всего вы должны написать реализацию и определение в отдельном файле из-за читаемости. Их можно поместить в один файл, но они не читаются. При написании кода напишите для следующего участника, который должен будет понять ваш код. Это важно, потому что следующим может быть вы:) Второй вопрос - это makefile. Сделать это сделать процесс компиляции проще, а не быстрее. Поэтому, если вы вносите изменения в любой из ваших файлов, вам не нужно вносить изменения и вносить файлы. Благодаря make, вам не нужно компилировать ваш файл по порядку снова и снова. Пишите файлы один раз, используйте каждый раз. Но если вы добавляете новый файл кода, который влияет на процесс компиляции, вы должны внести изменения в make-файл. Вы можете получить больше информации о читабельности, написании определения и реализации в отдельных файлах и обо всех других проблемах стиля в руководстве по стилю google C++ и о makefile в gnu make manuel.