Мне известны рекомендации не использовать using
в заголовочных файлах C++, причины этого широко обсуждались, например. вот .
Когда я дошел до этой статьи, описывающей «объявление псевдонима», я искал альтернативу typedef
, написанную на языке C++.
Теперь, просто потому, что понятия путаются в моей голове, мне неясно, предназначены ли рекомендации против использования using
в заголовках специально для такого способа его использования:
// my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H
using namespace std;
#endif
...или это также относится к объявлениям псевдонимов?
Из-за этой ситуации я хочу использовать объявления typedef/alias/что-то функционально эквивалентное:
// my_header.h
#ifndef MY_HEADER_H
#define MY_HEADER_H
#include <tuple>
// This function's signature is getting cumbersome
int func(const std::tuple<a::really::long_namespaced::thing, a::really::long_namespaced::thing, a::really::long_namespaced::thing>& tup = {1, 2, 3});
#endif
По сути, сигнатура функции громоздка из-за длины текста ее аргументов.
Каков общепринятый способ сделать это более читабельным, если using
для объявления псевдонима не рекомендуется в файлах заголовков?
@PepijnKramer — исправил long
в имени пространства имен — спасибо: P Это была оплошность, когда я только создавал полупсевдокод.
Книга «Ускоренный C++» посвящена C++98. В то время не было объявления псевдонима с using
. Итак, они говорят о других вариантах использования using
в заголовках. И они все еще есть.
Указание против using namespace std;
направлено против using namespace std;
, поскольку оно содержит большое количество символов и подвержено коллизиям. Специально для «проверки на будущее», поскольку в будущем в это пространство имен могут быть добавлены новые символы.
Простой пример будущей проверки, о которой пишет Элджей: godbolt.org/z/95dWhbTxf Компилятор обновляется, и код ломается, потому что новый компилятор по умолчанию использует C++17, в котором добавлена функция std::size
, конфликтующая с переменной size
.
Эх... Лично я ненавижу такие тупые рекомендации, потому что они отражают неправильную практику. Проблема с заголовками заключается в загрязнении пространства имен, а не в «использовании». Всегда плохо загрязнять пространство имен множеством несвязанных символов (особенно глобальное пространство имен). Не делай этого. Но существует множество допустимых вариантов использования объявлений using в соответствующих пространствах имен в заголовках.
Обычно рекомендуется избегать директив using
для пространств имен (например, using namespace std
) (а иногда и объявлений using
, таких как using std::string
). Причина в том, что они могут вызвать неоднозначность (например, using namespace std
делает неквалифицированную string
неоднозначной, если что-то с другим именем string
находится в безымянном пространстве имен), чего можно избежать, только удалив директиву using
(или всегда используя полные имена ::string
или std::string
, что отменяет обычное назначение директивы using
). Другие объявления using
(в основном) не вызывают таких побочных эффектов и полезны.
Здесь происходит несколько вещей. С помощью объявления using-dec , когда вы это делаете, using namespace std;
он импортирует все имена из std
в текущую область видимости. Если эта область действия является глобальной областью заголовочного файла, то все остальное, включающее этот файл, также получит такое имя, что может вызвать проблемы, поэтому настоятельно не рекомендуется делать это.
Второе, что касается использования объявлений, это то, что вы можете импортировать только одно имя из пространства имен, например using std::string;
, которое импортирует имя string
из std
в область видимости. Это менее плохо, чем using namespace std;
, но все же может вызвать проблемы, если в вашем коде есть другой тип string
.
Это подводит нас к using-alias, который использует слово using
, но ведет себя как объявление псевдонима, например typedef
. Это позволяет вам делать такие вещи, как
using string_t = std::string;
и тогда вы можете использовать string_t
вместо std::string
. Это позволяет избежать добавления string
из std
в область видимости, но при этом позволяет использовать его как string_t
вместо std::string
. Используя это с вашим примером кода, мы получаем
using thing_t = a_really_long_namespaced::thing;
int func(const std::tuple<thing_t, thing_t, thing_t>& tup = {1, 2, 3});
Ключевое слово using
имеет несколько значений:
using namespace X;
переносит все имена из пространства имен X
в текущее пространство имен. Это очень плохо для файлов заголовков, поскольку каждый пользователь этого заголовка рискует столкнуться с конфликтом имен с любым именем, существующим в X
. Особенно namespace std
имеет много очень распространенных имен, поэтому вы заставите пользователей вашего заголовка избегать большого количества хороших имен. Это также делает код хрупким, поскольку простое добавление еще одного заголовка в заголовок с using namespace
может сломать код в противоположном углу репозитория, который уже использует одно из имен.using X::Y;
переносят только одно имя Y
из пространства имен X
в текущее пространство имен. Например, using std::string;
позволяет использовать string
в качестве имени, но vector
по-прежнему требует std::
. Это по-прежнему может вызвать путаницу относительно того, что на самом деле представляет собой этот тип, но, по крайней мере, не приводит всех названий.using A = X::Y;
, чтобы ввести новое имя в ваше пространство имен. Вместо std::tuple<a::really::long_namespaced::thing, a::really::long_namespaced::thing, a::really::long_namespaced::thing>
вы можете указать новое имя, которое лучше описывает значение этого типа. На мой взгляд, в вашем случае это было бы предпочтительнее (std::tuple
не несет большого смысла, хорошо выбранное имя облегчит чтение, ввод и понимание).Кроме того, вы также можете использовать псевдонимы пространств имен. namespace shortName = a::really::long_namespaced;
может решить проблему с длинным пространством имен, часто используемым в вашем коде. Типичным примером из стандартной библиотеки является namespace fs = std::filesystem
, позволяющий писать, например. fs::path
вместо std::filesystem::path
. Это лучше, чем 2. ИМХО, потому что вы сохраняете некоторую ссылку на то, откуда взято имя.
Вы также можете объявить свою функцию в том же пространстве имен
namespace a::really::long_namespace{ int fun(const std::tuple<thing,thing,thing>) ... };
(И не используйте long в имени пространства имен.) Но в остальном это то, что есть, и просто используйте длинное (явное и правильное) пространство имен. Громоздкость вторична по сравнению с правильным ;)