6.13 – Безымянные и встраиваемые пространства имен
C++ поддерживает две модификации в обычных пространствах имен, о которых стоит хотя бы знать. Мы не будем опираться на них, поэтому пока считайте этот урок необязательным.
Безымянные (анонимные) пространства имен
Безымянное пространство имен (также называемое анонимным пространством имен) – это пространство имен, которое определяется без имени, например:
#include <iostream>
namespace // безымянное пространство имен
{
void doSomething() // доступно только в этом файле
{
std::cout << "v1\n";
}
}
int main()
{
doSomething(); // мы можем вызвать doSomething() без префикса пространства имен
return 0;
}
Эта программа напечатает:
v1
Все содержимое, объявленное в безымянном пространстве имен, рассматривается как часть родительского пространства имен. Таким образом, хотя функция doSomething
определена в безымянном пространстве имен, сама функция доступна из родительского пространства имен (которое в данном случае является глобальным пространством имен), поэтому мы можем вызывать doSomething
из main
без каких-либо квалификаторов.
Это может сделать безымянные пространства имен бесполезными. Но другой эффект безымянных пространств имен заключается в том, что все идентификаторы внутри безымянного пространства имен обрабатываются так, как если бы они имели внутреннее связывание, что означает, что содержимое безымянного пространства имен нельзя увидеть за пределами файла, в котором определено это безымянное пространство имен.
Для функций это фактически то же самое, что определение всех функций в безымянном пространстве имен как статических функций. Следующая программа практически идентична предыдущей:
#include <iostream>
static void doSomething() // доступно только в этом файле
{
std::cout << "v1\n";
}
int main()
{
doSomething(); // мы можем вызвать doSomething() без префикса пространства имен
return 0;
}
Безымянные пространства имен обычно используются, когда у вас много контента, который вы хотите, чтобы оставался локальным для данного файла, поскольку такой контент легче выделить в безымянное пространство имен, чем индивидуально отмечать все объявления как статические. Безымянные пространства имен также сохранят пользовательские типы (что мы обсудим на следующем уроке) локальными по отношению к файлу, для чего не существует альтернативного эквивалентного механизма.
Встраиваемые пространства имен
Теперь рассмотрим следующую программу:
#include <iostream>
void doSomething()
{
std::cout << "v1\n";
}
int main()
{
doSomething();
return 0;
}
Она напечатает:
v1
Довольно просто, правда?
Но, допустим, вам не нравится doSomething
, и вы хотите улучшить ее, изменив ее поведение. Но если вы сделаете это, вы рискуете сломать существующие программы, использующие старую версию. Как вы с этим справитесь?
Один из способов – создать новую версию функции с другим именем. Но в ходе многих изменений вы можете получить целый набор функций с почти одинаковыми именами (doSomething
, doSomething_v2
, doSomething_v3
и т.д.).
Альтернативой является использование встраиваемого (inline) пространства имен. Встраиваемое (inline) пространство имен – это пространство имен, которое обычно используется для создания версий содержимого. Подобно безымянному пространству имен, всё, что объявлено внутри встраиваемого пространства имен, считается частью родительского пространства имен. Однако встраиваемые пространства имен не ограничивают всё внутренним связыванием.
Чтобы определить встраиваемое пространство имен, мы используем ключевое слово inline
:
#include <iostream>
inline namespace v1 // объявить встраиваемое пространство имен с именем v1
{
void doSomething()
{
std::cout << "v1\n";
}
}
namespace v2 // объявить обычное пространство имен с именем v2
{
void doSomething()
{
std::cout << "v2\n";
}
}
int main()
{
v1::doSomething(); // вызывает версию v1 функции doSomething()
v2::doSomething(); // вызывает версию v2 функции doSomething()
doSomething(); // вызывает встраиваемую версию doSomething() (это v1)
return 0;
}
Эта программа напечатает:
v1
v2
v1
В приведенном выше примере функции, вызывающие doSomething
, получат v1
(встраиваемую версию) doSomething
. Вызывающие функции, которые хотят использовать более новую версию, могут явно вызвать v2::dosomething()
. Это сохраняет работоспособность существующих программ, позволяя новым программам использовать преимущества новых/улучшенных изменений.
В качестве альтернативы, если вы хотите продвигать более новую версию:
#include <iostream>
namespace v1 // объявляем обычное пространство имен v1
{
void doSomething()
{
std::cout << "v1\n";
}
}
inline namespace v2 // объявляем встраиваемое пространство имен v2
{
void doSomething()
{
std::cout << "v2\n";
}
}
int main()
{
v1::doSomething(); // вызывает версию v1 функции doSomething()
v2::doSomething(); // вызывает версию v2 функции doSomething()
doSomething(); // вызывает встраиваемую версию doSomething() (это v2)
return 0;
}
Эта программа напечатает:
v1
v2
v2
В этом примере все функции, вызывающие doSomething
, по умолчанию получат версию v2
(более новую и лучшую версию). Пользователи, которым по-прежнему нужна старая версия doSomething
, для получения доступа к старому поведению могут явно вызвать v1::doSomething()
. Это означает, что существующие программы, которым нужна версия v1
, должны будут глобально заменить doSomething
на v1::doSomething
, но обычно это не вызывает проблем, если функции имеют правильные имена.