Файлы заголовков С++, разделение кода

Я новичок в С++, и у меня было несколько общих вопросов о разделении кода. В настоящее время я создал небольшое приложение, все в одном файле. То, что я хочу сделать сейчас, это преобразовать это в отдельные файлы, чтобы они содержали аналогичный код или еще много чего. Мой настоящий вопрос прямо сейчас, как я могу узнать, как отделять вещи? Каков невидимый запас, который должен разделять код?

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

Любое понимание методов или лучших практик было бы здорово, спасибо!

Ответы

Ответ 1

Заголовочные файлы должны содержать объявления классов и функций.

Исходные файлы содержат определения классов и функций.

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

Пример: Меню класса

Menu.h:     Contains the Menu declaration.
Menu.cpp:   Contains the Menu definition.

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

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

Ответ 2

Во-первых, вы не должны помещать что-либо в заголовки, которые не должны быть видимыми каким-либо другим файлом, отличным от того, который ему нужен. Затем определим что-то, что нам нужно ниже.

Модуль перевода

Модуль перевода - это текущий код, который скомпилирован, и весь код включен им прямо или косвенно. Один модуль перевода переводится в один файл .o/.obj.

Программа

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

Каковы основные моменты наличия разных единиц перевода?

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

Теперь, как вы можете разделить свой код на разные единицы перевода? Ответ заключается в том, что нет "так вы это делаете!", Но вы должны рассматривать его в каждом конкретном случае. Часто бывает ясно, что у вас разные классы, которые можно и нужно поместить в разные единицы перевода:

foo.hpp:

/* Only declaration of class foo we define below. Note that a declaration
 * is not a definition. But a definition is always also a declaration */
class foo;

/* definition of a class foo. the same class definition can appear 
   in multiple translation units provided that each definition is the same  
   basicially, but only once per translation unit. This too is called the  
   "One Definition Rule" (ODR). */
class foo {
    /* declaration of a member function doit */
    void doit();

    /* definition of an data-member age */
    int age;
};

Объявите некоторые свободные функции и объекты:

/* if you have translation unit non-local (with so-called extern linkage)  
   names, you declare them here, so other translation units can include  
   your file "foo.hpp" and use them. */
void getTheAnswer();

/* to avoid that the following is a definition of a object, you put "extern"  
   in front of it. */
extern int answerCheat;

foo.cpp:

/* include the header of it */
#include "foo.hpp"

/* definition of the member function doit */
void foo::doit() {
    /* ... */
}

/* definition of a translation unit local name. preferred way in c++. */
namespace {
    void help() {
        /* ... */
    }
}

void getTheAnswer() {
    /* let call our helper function */
    help();
    /* ... */
}

/* define answerCheat. non-const objects are translation unit nonlocal  
   by default */
int answerCheat = 42;

bar.hpp:

/* so, this is the same as above, just with other classes/files... */
class bar {
public:
    bar(); /* constructor */
}; 

bar.cpp:

/* we need the foo.hpp file, which declares getTheAnswer() */
#include "foo.hpp"
#include "bar.hpp"

bar::bar() {
    /* make use of getTheAnswer() */
    getTheAnswer();
}

Обратите внимание, что имена в анонимном пространстве имен (как указано выше) не конфликтуют, поскольку они кажутся локальными единицами перевода. на самом деле это не так, у них просто уникальные имена, чтобы они не столкнулись. если вы действительно хотите (нет оснований) для перевода единиц локальных имен (например, из-за совместимости с c, поэтому код C может вызвать вашу функцию), вы можете сделать это следующим образом:

static void help() { 
    /* .... */
}

ODR также говорит, что вы не можете иметь более одного определения любого объекта или не встроенной функции в одной программе (классы - это типы, а не объекты, поэтому к ним они не применяются). Таким образом, вы должны следить за тем, чтобы не помещать не-встроенные функции в заголовки или не помещать объекты типа "int foo;" в заголовках. Это вызовет ошибки компоновщика тогда, когда компоновщик попытается связать единицы перевода, включая эти заголовки.

Надеюсь, я немного помогу вам. Теперь это был длинный ответ, где-то есть ошибки. Я знаю, что единица перевода строго определяется другим способом (выход препроцессора). Но я думаю, что это не добавило бы большой ценности, чтобы включить это в вышеупомянутое, и это смутит вопрос. Пожалуйста, не стесняйтесь, если вы найдете настоящие ошибки:)

Ответ 4

Решение о том, как отделить ваш код от разных классов/функций, является одной из основных задач программирования. Существует много разных рекомендаций относительно того, как это сделать, и я бы рекомендовал прочитать некоторые учебники по С++ и объектно-ориентированному дизайну, чтобы вы начали.

Некоторые основные рекомендации будут

  • Объединяйте вещи, которые используются вместе
  • Создание классов для объектов домена (например, файлы, коллекции и т.д.)

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

// A.h
class A
{
public:
    int fn();
};

Затем вы можете использовать этот класс в нескольких исходных файлах:

// A.cpp
#include "A.h"
int A::fn() {/* implementation of fn */}

//B.cpp
#include "A.h"
void OtherFunction() {
    A a;
    a.fn();
}

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

// B.cpp
#include  "A.cpp" //DON'T do this!

Затем вы можете скомпилировать B.cpp, но когда вы попытаетесь связать свою программу, компоновщик будет жаловаться, что у вас есть многократно определенные объекты - это потому, что у вас есть несколько копий реализации A.

Ответ 5

Предложение: 1. У вас есть готовый дизайн для вашего приложения. 2. На основе дизайна создайте необходимые объекты, которые взаимодействуют друг с другом. 3. Рефакторинг или полностью изменить существующий код в соответствии с недавно созданным дизайном.

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