Файл шаблона (.tpp) включает защиту

При написании шаблонных классов мне нравится перемещать реализацию в другой файл (myclass.tpp) и включать ее в конец основного заголовка (myclass.hpp).

Мой вопрос: нужно ли мне включать охранники в файл .tpp или достаточно, чтобы они были в файле .hpp?

Пример кода:

myclass.hpp

#ifndef MYCLASS_HPP
#define MYCLASS_HPP

template<typename T>
class MyClass
{
public:
    T foo(T obj);
};

//include template implemetation
#include "myclass.tpp"

#endif

myclass.tpp

#ifndef MYCLASS_TPP //needed?
#define MYCLASS_TPP //needed?

template<typename T>
T MyClass<T>::foo(T obj)
{
    return obj;
}

#endif //needed?

Они должны присутствовать во всех заголовочных файлах, которые не предполагается встраивать несколько раз. Если предполагается, что myclass.tpp будет включен только в myclass.hpp, то имеет смысл добавить дополнительные охранники.

user7860670 25.01.2019 10:56

Я бы пошел еще дальше и добавил описательную директиву #error, если защита hpp не определена. Просто чтобы немного защитить от людей, в том числе от tpp.

StoryTeller - Unslander Monica 25.01.2019 11:01

Ваш .tpp будет включен другими? если нет (я думаю), то зачем его охранять?

apple apple 25.01.2019 11:16

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

apple apple 25.01.2019 11:19

плюс я не думаю, что кто-то будет напрямую включать .tpp, как и .cpp

apple apple 25.01.2019 11:23

Возможно, вы сможете заменить все эти защитные средства на #pragma once, поскольку он широко поддерживается.

quamrana 25.01.2019 12:03

@quamrana Гарды лучше #pragma once во всем. Нет причин помещать нестандартную версию в новый код.

Leushenko 25.01.2019 16:32
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
7
2 216
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Do I need include guards in the .tpp file or is it sufficient to have them in the .hpp file?

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

  • Ужасно полезно: они позволяют объявить зависимость от нескольких файлов, не отслеживая, какие файлы уже были включены.
  • Дешевый: это всего лишь некоторые токены прекомпиляции.
  • Неразрушающий: они хорошо подходят для большинства вариантов использования #include (у меня был коллега, который не умел писать макросы, поэтому он #included реализовал файлы фейспалм).
  • Ожидал: разработчики знают, что это такое, и почти не замечают их; напротив, отсутствие в заголовочном файле include guards будит нас и добавляет к глобальному счетчику wtf/line.

Я пользуюсь возможностью, чтобы выделить комментарий от StoryTeller:

I'd go a step further and add a descriptive #error directive if the hpp guard is not defined. Just to offer a little protection from people including the tpp first.

Что будет переведено на:

#ifndef MYCLASS_TPP
#define MYCLASS_TPP

#ifndef MYCLASS_HPP
#error __FILE__ should only be included from myclass.hpp.
#endif // MYCLASS_HPP

template<typename T>
T MyClass<T>::foo(T obj)
{
    return obj;
}

#endif // MYCLASS_TPP

Примечание: если блок трансляции сначала #include <myclass.hpp>, а потом #include <myclass.tpp>, то ошибки не вылетает и все нормально.

Хороший! Хотя, если я предотвратил включение файла .tpp напрямую, выдав эту ошибку, не будет ли определение MYCLASS_TPP избыточным?

dave 25.01.2019 11:24

@dave Нет. Если ваш пользователь сначала включает myclass.hpp, а затем myclass.tpp, ошибка не сработает.

Max Langhof 25.01.2019 11:26

Я не согласен с предложением включать охрану никогда не нужно. Предположим, что заголовок A.hpp должен включать другой заголовок B.hpp (поскольку, например, он использует определения, найденные в B.hpp), и предположим, что вы пишете код, который требует и A.hpp, и B.hpp. Если вы не проверите код A.hpp, вы не узнаете, что #include "B.hpp" не нужен. Затем вы должны включить как A.hpp, так и B.hpp, что приведет к двойному включению B.hpp. Защита включения предотвращает это.

francesco 25.01.2019 11:43

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

YSC 25.01.2019 11:44

@francesco Необычный ответ будет таким: «#pragma once также может сделать это практически в любом компиляторе».

Max Langhof 25.01.2019 11:45

@MaxLanghof Эта городская легенда, в которой есть охранники, безопаснее, чем pragma once, должна быть уничтожена раз и навсегда. Все аргументы, которые применяются против pragma once, относятся к включению защиты. В конце концов, я предпочитаю, чтобы системный процесс обеспечивала машина, а не человек.

Oliv 25.01.2019 11:48

@Oliv #pragma once, хотя в принципе поддерживается всеми компиляторами, тем не менее имеет поведение, определяемое реализацией и работает не во всех настройках сборки. (Если это работает для вас, это нормально, но это только ваши личные предпочтения. Кроме того, я даже не уверен в том, что это определяется реализацией, потому что это требует документирования поведения.)

Arne Vogel 28.01.2019 11:35

@ArneVogel На этой странице MSVC рекомендуется pragma once * Он не является частью стандарта C++, но реализуется с возможностью переноса несколькими распространенными компиляторами. * ссылка на сайт Для Gcc ссылка на сайт. Для Clang см. Gcc (у ​​clang до сих пор нет собственной полной документации!). Согласно википедия, только Cray еще ни разу не реализовал прагму. Если один целевой Cray, код, безусловно, не будет переносимым вообще.

Oliv 28.01.2019 15:59

Просто используйте pragma once во всех файлах заголовков. Компилятор гарантирует, что ваш файл будет включен только один раз. Компилятор может не распознать только в очень необоснованном состоянии: кто-то структурирует включаемые каталоги с помощью жесткой ссылки. Кто это делает? Если кто-то не может найти уникальное имя для своего файла, зачем ему лучше находить уникальное имя для каждой защиты включения для всех заголовочных файлов?

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

Как выбираются уникальное имя макроса: <project name>_<filename>? Как она может быть более уникальной, чем уникальность, основанная на всей структуре корневого каталога?

Итак, в конце концов, при выборе между include guard или pragma once следует учитывать стоимость работы, которая необходима для обеспечения уникальности:

1 - Для pragma once вам нужно только убедиться, что структура каталога вашей системы не перепутана благодаря жестким ссылкам.

2 - Для включить охрану для каждого файла в вашей системе вы должны убедиться, что имя макроса уникально.

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

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

YSC 25.01.2019 13:09

@YSC Не писать, а поддерживать старую библиотеку. Для новой библиотеки, которую можно добавить в старую систему, вы просто указываете, что эта библиотека не должна быть жестко связана, и все. Нет никаких причин заставлять всех платить за древнюю практику, которая все еще может использоваться многими людьми, которых можно сосчитать с одной стороны. Это все равно, что просить миллионы программистов потратить свое время на то, чтобы невероятно повысить производительность 2-х или 3-х системных администраторов.

Oliv 25.01.2019 13:21

Я думал о компиляторах, слишком старых/непонятных, чтобы знать #pragma once ;) Но да.

YSC 25.01.2019 13:22

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

Francis Cugler 25.01.2019 15:32

Другие вопросы по теме