Когда вам нужно использовать #include в C ++ - PullRequest
6 голосов
/ 02 марта 2011

Я уже некоторое время программирую, и одна вещь, которую я до сих пор так и не понял, - это когда вам нужно #include что-то. Я знаю, чтобы быть в безопасности, вы можете сделать это всякий раз, когда вы используете что-то объявлено в другом файле. Однако иногда я обнаруживаю, что могу удалить #include, и все будет прекрасно скомпилироваться. Из того, что я могу сказать, это потому, что другие включаемые файлы уже включают внешнее определение. Есть два частных случая, в которых мне интересно знать поведение:

  1. Скажем, у нас есть три пары .h / .cc: f1.h / .cc, f2.h / .cc и f3.h / .cc. Если f2.h / .cc включает в себя f1.h, а f3.h / .cc включает в себя f2.h, будет ли когда-либо необходимо, чтобы f3.h / .cc включил f1.h, или все определения f1.h будут видны для F3 файлы, когда он включен в F2?

  2. Еще раз скажем, у нас есть три пары .h / .cc: f1.h / .cc, f2.h / .cc и f3.h / .cc. Если f2 включает в себя f1 и f2 включает в себя f1, а затем f3 включает в себя f1 или f2, вызовет ли «круговая связь» между и f1 и f2 проблему?

Вам известны какие-либо полезные ресурсы в Интернете, которые я могу прочитать, чтобы лучше понять, как включение чего-либо в один файл влияет на последующие файлы в проекте?

Ответы [ 5 ]

6 голосов
/ 02 марта 2011

В этом нет ничего особенного.Если вы используете что-то, вы должны включить заголовок, объявляющий то, что вы используете.Единственным исключением является прямое объявление класса / структуры или метода, например:

class myclass;   

, если вам просто нужно объявить указатель или ссылку на класс.

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

2 голосов
/ 02 марта 2011

Вопрос 1: Я думаю, что все, что вам не хватает, это разница между "f2 включает в себя f1", и "f2 это гарантировано для включения f1".Это особенно важно для стандартных заголовков, поскольку любой стандартный заголовок позволяет включать любые другие.Поэтому, если вы полагаетесь на косвенные включения, которые работают на вашем компьютере, ваш код может не скомпилироваться в другой реализации C ++.

Если у вас есть библиотека, в которой документация для «f2.h» говорит или подразумевает, чтоон включает в себя «f1.h», что означает, что он всегда будет во всех совместимых версиях, поэтому вы можете положиться на косвенное включение.Вы можете сделать это, когда вы используете один компонент библиотеки, который в своей основе опирается на другой компонент этой библиотеки, но где другой компонент может использоваться изолированно другими пользователями.В качестве гипотетического примера «xhtml_parser.h» может обоснованно задокументировать, что он предоставляет все определения из «xml_parser.h», а также некоторые дополнительные.

Вопрос 2: Хм, вы бы хотели перефразировать вопрос?«f2 включает в себя f1 и f2 включает в себя f1» - это не то, что вы имели в виду, и нет «круговой связи».Это может вызывать проблемы, если вы пишете заголовки так, что f1 включает в себя f2, а f2 включает в себя f1, потому что include - это не "linkage", это в значительной степени вырезка и вставка содержимого другого заголовочного файла.

Таким образом, даже до того, как на снимке появится f3, циклические включения могут быть проблематичными:

f1.h
----
#ifndef f1_h_included
#define f1_h_included

#include "f2.h"
struct DerivedA : BaseA {};
struct BaseB {};

#endif

f2.h
----
#ifndef f2_h_included
#define f2_h_included

#include "f1.h"
struct BaseA {};
struct DerivedB : BaseB {};

#endif

Это не скомпилируется, независимо от того, что вы включаете из "f1.h" и "f2.час".Предполагая, что f1 включается первым, результат после предварительной обработки выглядит следующим образом:

// contents of f2.h, pasted in at line 4 of f1.h
// (contents of f1.h on the circular include are ignored due to include guard)
struct BaseA {};
struct DerivedB : BaseB {};

// rest of f1.h
struct DerivedA : BaseA {};
struct BaseB {};

И поэтому DerivedB определяет базовый класс, который еще не был определен.Включите их наоборот, та же проблема с DerivedA.

1 голос
/ 02 марта 2011

Однако иногда я обнаруживаю, что могу удалите #include и все будет по-прежнему компилируется просто отлично. Из чего я могу сказать, что это потому, что другие файлы включены уже в том числе внешнее определение.

Правильно. Это просто из-за удачи, вроде.

Скажем, у нас есть три пары .h / .cc: f1.h / .cc, f2.h / .cc и f3.h / .cc. Если f2.h / .cc включает в себя f1.h и f3.h / .cc включает в себя f2.h это когда-либо необходимо для f3.h / .cc включить f1.h или все из определения f1.h быть видимым для файлы f3, когда он включен в f2

Вы, вероятно, имели в виду объявления f1.h , а не определения , хотя у вас могут быть некоторые определения функций классов и шаблонов.

В любом случае, ответ - нет, он никогда не будет необходим. Эти декларации будут видны. Директивы препроцессора - это просто вставка текста. Как только вы представите себе это в своей голове, расширение станет легко понять.

Еще раз скажем, у нас есть три .h / .cc пары: f1.h / .cc, f2.h / .cc и f3.h / .cc. Если f2 включает в себя f1 и f2 включает в себя f1, а затем f3 включает в себя f1 или F2 будет "круговая связь" между а f1 и f2 вызывают проблемы?

Да, возможно. Защита заголовка может облегчить это, если содержание заголовочных файлов нормальное. Но если содержимое для f2 полагается на содержимое для f1 и наоборот, то в коде есть циклическая зависимость. Это должно быть решено путем удаления этой циклической зависимости с использованием предварительных объявлений .

Знаете ли вы какие-либо хорошие ресурсы онлайн я могу прочитать, чтобы лучше понять как включить что-то в один файл влияет на последующие файлы в проект?

Я могу порекомендовать эти ресурсы .

1 голос
/ 02 марта 2011

У тебя это довольно сильно прибито. Для того, чтобы использовать потоки ввода / вывода мне нужно включить заголовочный файл для этого. Если вы написали класс bigint, который поддерживает большой целочисленный тип, и вы отправили этот класс другу. Ваш друг должен будет включить это в свою программу, чтобы использовать это. Таким образом, вы включаете что-то, когда это не доступно вашей программе.

0 голосов
/ 02 марта 2011

Сначала краткие ответы, последуют объяснения:

1) нет, никогда не нужно объявлять #include "f1.h" в f3.h / .cc, потому что это сделает включение-цикл (что не следует делать)

2) в 1-> 2, 1-> 2-> 3 цикл отсутствует, даже 2 будут включены только один раз, если вы используете защиту включения (#ifndef 2 #define 2 my2code #endif)

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

например, myheader.h:

#ifndef _myheader_h_
#define _myheader_h_
int myglobal;
#endif

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

...