Заголовки C ++ и файлы cpp — как, черт возьми, мне управлять ими?

Я получаю сумасшедшие, загадочные ошибки компилятора при попытке построить свое решение на VS2010. Мне действительно трудно понять, как это работает.

Game.cpp
Game.h
Deck.cpp
Deck.h
Card.h

// Game.cpp
#include "Game.h"

Все хорошо. Теперь мне нужно создать новую колоду:

// Game.h
private:
static Deck _deck;

Ну, тогда мне нужно включить Deck.h, чтобы он знал, что это такое:

// Game.h
#include "Deck.h"class Game {
private:
Deck _deck;
}

Хорошо, это хорошо. Но теперь мне нужно использовать _deck в Game.cpp

// Game.cpp
#include "Game.h"Deck Game::_deck;
void Shuffle(void)
{
_deck = Deck();
_deck.Shuffle();
}

Но я получаю сообщение об ошибке, говорящее, что «Палуба не определена». Но поскольку Game.cpp включает Game.h, должен ли Game.h включать Deck.h?

Если я добавлю Deck.h в Game.cpp, я получу:

"Uses undefinied class Deck Game.cpp"

а также

"Deck class redeclaration Deck.h"

Я совсем этого не понимаю …

1

Решение

Вы должны увидеть этот вопрос, описывающий включение защиты

Почему #ifndef и #define используются в заголовочных файлах C ++?

2

Другие решения

У вас есть циркулярное включение. К счастью для вас, static член не требует полного определения при объявлении, поэтому вы можете включить его в Game.h с помощью предварительного объявления:

// Game.h
class Deck;

//....
private:
static Deck _deck;
1

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

Проблема, которую вы описываете, связана с переводческие единицы. По сути, вы можете думать, что исходный файл (.c, .cpp, .inl и т. Д.) Отличается от файла заголовка (.h, .hpp) тем, что каждый исходный файл определяет единицу перевода, которую компилятор может действуют на. Исходный файл, по сути, состоит из всей логики в файле, а также всех включений, на которые он прямо или косвенно ссылается.

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

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

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

1
По вопросам рекламы [email protected]