Можно ли написать заголовочный файл без включенного охранника и без множественных ошибок определения? - PullRequest
3 голосов
/ 18 марта 2012

Просто из любопытства я хотел узнать, есть ли способ достичь этого.

В C ++ мы узнаем, что мы должны избегать использования макросов. Но когда мы используем include guard, мы используем по крайней мере один макрос. Поэтому мне было интересно, есть ли способ написать программу без макросов.

Ответы [ 5 ]

3 голосов
/ 18 марта 2012

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

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

IncludedFile.h

class SomeClassSomewhere;
void SomeExternalFunction(int x, char y);

main.cpp

#include "IncludedFile.h"
#include "IncludedFile.h"
#include "IncludedFile.h"

int main(int argc, char **argv)
{
    return 0;
}

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

В C ++ заголовочные файлы очень часто включают определения классов. Защита от включения предотвращает вставку файла #included во второй файл во второй раз, что означает, что ваши определения появятся только один раз в скомпилированном коде, и компоновщик не будет сбит с толку.

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

1 голос
/ 18 марта 2012

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

  • Объявления могут повторяться несколько раз, хотя они, очевидно, должны быть согласованными, и некоторые объекты не могут быть объявлены (например, enum может быть определено только; в C ++ 2011 можно также объявить enum s).
  • Определения не могут быть повторены, но они нужны только тогда, когда определение действительно используется. Например, использование указателя или ссылки на класс не требует его определения, а только его объявления.

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

Хотя у этого подхода есть несколько преимуществ, он также имеет несколько серьезных недостатков. Основное преимущество заключается в том, что он обеспечивает очень тщательное разделение и управление зависимостями. С другой стороны, чрезмерно агрессивное разделение, например использование pimpl-idiom для всего также отрицательно сказывается на производительности. Самый большой недостаток заключается в том, что многие детали реализации неявно видны пользователю заголовка, потому что соответствующие заголовки, от которых он зависит, должны быть включены сначала явно. По крайней мере, компилятор обеспечивает правильный порядок включения файлов.

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

1 голос
/ 18 марта 2012

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

Кроме того, вы можете использовать:

#pragma once

, если переносимость не является вашей задачей.

Однако вам следует избегать использования # прагмы один раз более Включить охрану , поскольку:

  • Он не является стандартным и, следовательно, не переносимым.
  • Он менее интуитивен, и не все пользователи могут знать о нем.
  • Он не дает большого преимущества перед классическим и очень хорошо известнымОхранники.
1 голос
/ 18 марта 2012

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

Макросы препроцессора не одобряются, да.Однако защита заголовка включает в себя необходимое зло, потому что альтернатива намного хуже (#pragma один раз сработает, только если ваш компилятор его поддерживает, поэтому вы потеряете переносимость)

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

0 голосов
/ 18 марта 2012

Достаточно ли хорошо для вас работает нестандартный

#pragma once

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

...