Заголовочные файлы C ++, разделение кода - PullRequest
29 голосов
/ 11 ноября 2008

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

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

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

Ответы [ 4 ]

25 голосов
/ 11 ноября 2008

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

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

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

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

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

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

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

20 голосов
/ 11 ноября 2008

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

Блок перевода

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

Программа

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

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

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

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

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's 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 голосов
/ 11 ноября 2008

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

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

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

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

// 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.

0 голосов
/ 18 февраля 2009

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

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

...