(избегать) разделения кода на .cpp и .h в C ++ и эффективной компиляции

Обычной практикой в ​​C ++ является разделение объявлений в .h (или .hpp) и реализации в .cpp.

Я знаю две основные причины (может быть, есть и другие):

  1. Скорость компиляции (вам не нужно перекомпилировать все, когда вы меняете только один файл, вы можете связать его make из предварительно скомпилированных .o файлов)
  2. Иногда необходимы предварительные объявления (когда реализация 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

Однако я часто нахожу это очень неудобным

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

  1. Когда вы вносите существенные изменения в структуру классов, вам нужно постоянно переключаться между .cpp и .h частью кода.
  2. У вас вдвое больше файлов в редакторе и в папке проекта, что сбивает с толку.
  3. Вам нужно дважды написать некоторую информацию (например, заголовки функций или QuadraticFunc::), где вы можете сделать много опечаток и несоответствий, поэтому компилятор все время жалуется (я делаю такие ошибки очень часто)
  4. Каждый раз, когда вы добавляете / удаляете / переименовываете какой-либо класс, вам нужно отредактировать 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) что мне гораздо удобнее?


person Prokop Hapala    schedule 28.12.2015    source источник
comment
AFAIK, нет, и это создаст очень большую проблему: циклические зависимости.   -  person Danh    schedule 28.12.2015
comment
Несвязано, но нет причин динамически размещать объект QuadraticFunc в main. Просто используйте автоматическую переменную.   -  person juanchopanza    schedule 28.12.2015


Ответы (4)


Однако теперь, когда я начинаю писать более сложные программы, время компиляции становится довольно большим, когда мне приходится все время перекомпилировать все.

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

Когда у вас есть class1.h, class1.cpp, class2.h, class2.cpp, ..., classN.h и classN.cpp, эти заголовки включаются только в скомпилированные объекты каждого класса. Поэтому, если логика вашей функции изменяется в class2, а ваш заголовок - нет, вам нужно только скомпилировать class2 в объектный файл. Затем вы выполните связывание всех объектных файлов, которые создают ваш фактический исполняемый файл. Связывание происходит БЫСТРО.

Если вы создаете большие и сложные программы и обнаруживаете, что редактирование заголовков является проблемой, подумайте о РАЗРАБОТКЕ своего приложения, прежде чем писать его.

person Simo Erkinheimo    schedule 28.12.2015
comment
да, именно так, это изначальная мотивация вопроса - person Prokop Hapala; 28.12.2015
comment
Что касается опечаток и переключения между заголовком и файлом класса, хорошая IDE - это то, что вам нужно. Некоторые IDE даже замечают, что вы редактировали и не редактировали, и строят только то, что необходимо. Когда IDE обрабатывает сборку, вам может даже не понадобиться видеть make-файл. - person Simo Erkinheimo; 28.12.2015
comment
это, вероятно, правда (нехорошо сравнивать Java, где я использую Netbeans, с C / C ++, где я использую gedit и gcc). Но все же тот факт, что мне не нужно использовать какую-либо тяжелую IDE с C / C ++ и что я могу контролировать то, что происходит под хижиной, я считаю одним из преимуществ программирования на C / C ++. - person Prokop Hapala; 28.12.2015
comment
Обычно использование IDE окупает время, которое вы теряете, когда не используете его, даже если IDE была немного тяжелой (по сравнению с gedit). Не уверен в его функциях, но codelite.org считается одним из самых легких. - person Simo Erkinheimo; 28.12.2015

Короткий ответ: нет.

Длинный ответ: все равно нет.

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

У меня лично нет проблем с использованием двух файлов. Большинство редакторов поддерживают «просмотр двух файлов» - и большинство из них также поддерживают «переход к определению».

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

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

Конечно, используйте make [или что-то подобное] с правильно определенными зависимостями для сборки вашего проекта.

person Mats Petersson    schedule 28.12.2015

C ++ - это C ++, а Java - это Java. Разделение исходного кода на файлы .h- и .cpp является частью концепции языка C ++. Если вам это не нравится, вы не должны его использовать.

Помещение всего в один заголовочный файл практически то же самое, что и включение файла .cpp (который работает, но крайне неуместен). Вы не должны этого делать, если:

  • написание стандартных классов, функций, ...
  • многократное использование раздела кода в больших программах (в противном случае приводит к ошибке переопределения, если вы включаете все в main.cpp)
  • вы хотите передать часть своей программы в статическую / динамическую библиотеку. Практически каждая доступная созданная библиотека работает именно так.

Примеры: WindowsAPI (COM !!), SFML, Boost (частично) (и многое другое)

Вы можете сделать это, когда:

  • код делает очень простые вещи, например Битовый сдвиг (создание цветовых кодов), анализ строк, ...

Примеры: повышение (частичное)

Вы должны сделать это, когда:

  • Создание классов или функций шаблонов по мере их создания во время выполнения компиляции. Это один из основных и наиболее обсуждаемых недостатков концепции .h / .ccp, поэтому вы не первый, кто задумывается об этом.

Примеры: STL (стандартная библиотека шаблонов C ++)

person BeschBesch    schedule 28.12.2015
comment
о, шаблоны не будут работать с разделением .cpp и .h? Это очень плохо ... Я много занимаюсь низкоуровневой математикой с интенсивной производительностью, где использую шаблоны и встроенные функции. Собственно, это одна из основных причин, почему Java меня не устраивает. - person Prokop Hapala; 28.12.2015
comment
См. вопрос для получения дополнительной информации. - person BeschBesch; 28.12.2015
comment
объявление ошибка переопределения ... Я включаю все только в main.cpp, поэтому, думаю, ошибки повторного объявления быть не может - person Prokop Hapala; 28.12.2015
comment
Putting everything in one header-file is practially the same as including the .cpp file ... да, именно так. Возможно, мне следовало сформулировать это как including .cpp file в исходном вопросе, чтобы избежать путаницы. - person Prokop Hapala; 28.12.2015
comment
Пример: вы определяете базовый класс в a.h (реализация, статические переменные, могут быть некоторые глобальные переменные), включаете его в b.h и c.h, потому что он вам нужен здесь, а затем включаете b.h и c.h в main.pp. Вы бы переопределили базовый класс, и это могло бы привести к ошибкам компилятора. @ Second: Нет, не делайте этого, вы никогда не включаете .cpp-файлы. #include используется только для файлов .h. - person BeschBesch; 28.12.2015
comment
Если я всегда компилирую только main.cpp, мне не нужно включать a.h в c.h и b.h. Мне нужно только убедиться, что a.h включен в main.cpp, прежде чем включать b.h и c.h ... но я понимаю, что вы имеете в виду. - person Prokop Hapala; 28.12.2015

Прежде всего, вам нужно написать реализацию и определение в отдельном файле из-за читабельности. Их можно поместить в один файл, но он не читается. При написании кода напишите для следующего участника, который должен будет разбираться в вашем коде. Это важно, потому что следующим можете быть вы :) Вторая проблема - это makefile. Make упрощает процесс компиляции, а не ускоряет его. Поэтому, если вы вносите изменения в любой из своих файлов, вам не нужно вносить изменения и создавать файлы. Благодаря make вам не нужно снова и снова компилировать файл по порядку. Запишите файлы один раз, используйте каждый раз. Но если вы добавляете новый файл кода, который влияет на процесс компиляции, да, вам нужно внести изменения в make-файл. Вы можете получить дополнительную информацию о удобочитаемости, написании определения и реализации в отдельных файлах, а также обо всех других проблемах стиля в руководстве по стилю Google C ++ и о make-файле в gnu make manuel.

person O.AYYILDIZ    schedule 28.12.2015
comment
Читаемость субъективна. - person emlai; 28.12.2015