Anonymous namespace в хэдэрах
Безымянные пространства имен звучат, как хорошая альтернатива static. Была даже тема, что в С++11 задеприкейтили static для свободных функций и переменных в угоду замены на anonymous namespace. Однако от этой идеи отказались, чтобы сильно с сишечкой не расходиться.
Когда нам рассказывают про такой мощный инструмент и облизывают его со всех сторон, то нужно проверить, реально ли он так хорош во всех ситуациях. Сегодня поговорим про его использование в хэдэрах.
Открываем С++ Core Guidelines и видим, что там написано "не используйте анонимные пространства имен в заголовочных файлах". Эх, а так хотелось...
Это рекомендация практически полностью основана на том факте, что сущности в безымянных неймспейсах имеют внутреннее связывание. И это может привести к следующим проблемам:
1️⃣ Раздувается бинарный файл. Во всех единицах трансляции, куда включен ваш хэдэр, будут содержаться копии сущностей из анонимного пространства имен. На копии тратится память и, ну вы поняли...
2️⃣ Вы рискуете нарваться на неопределенное поведение. Рассмотрим такой пример:
Правило для встроенных функций заключается в том, что их определения во всех единицах трансляции должны быть идентичными. В примере выше каждая единица перевода имеет отдельный экземпляр pi. И результирующее определении twoPiR будет выбрано рандомно из всех юнитов, поэтому в нем будет локальным адрес pi из какого-то одного юнита. Соотвественно, использование локального адреса одной единицы трансляции в другой ведет к UB.
Конечно, даже без анонимного пространства имен это было бы неопределенное поведение здесь (поскольку const означает внутреннюю линковку по умолчанию), но основной принцип сохраняется. Любое использование в заголовке чего-либо в неназванном пространстве имен (или любого объекта const, определенного в заголовке), скорее всего, вызовет неопределенное поведение. Является ли это реальной проблемой или нет, зависит, от того, как будет обработана переменная pi. Скорее всего в этом конкретном случае компилятор просто вставит значение 3.14159 в место использования. И тогда никакой зависимости от других единиц трансляции не будет. Но для более сложных объектов не стоит надеятся на такие оптимизации.
3️⃣ Внутри одной единицы трансляции все анонимные неймспейсы на самом деле являются одним пространством с уникальным для юнита именем. Это значит, если в двух заголовочниках у вас будут такие строчки:
и вы подключите их в один исходник, то произойдет нарушение ODR. У вас будут два определения одной и той же сущности внутри одного пространства имен.
Сложно контролировать имена объектов и функций, которые мы располагаем в заголовочниках, чтобы они не пересекались с именами у других хэдэрах. Поэтому, если в какой-то момент вы будете вынуждены подключить два заголовочника(код которых уже где-то используется) с пересекающимися именами в своих anonymous namespace в один цппшник, то будут проблемки. Придется переименовывать какую-то половину сущностей во всем проекте, где они использовались.
До С++17 у нас особо не было универсального и удобного способа распространять код только через хэдэра. Теперь же есть inline переменные. И мы можем определять внешнесвязные сущности в хэдэрах. Это полностью решило все перечисленные здесь проблемы.
Поэтому используйте inline функции и переменные в хэдэрах и радуйтесь жизни!
Enjoy life. Stay cool.
#cpp11 #cpp17 #compiler #cppcore
Безымянные пространства имен звучат, как хорошая альтернатива static. Была даже тема, что в С++11 задеприкейтили static для свободных функций и переменных в угоду замены на anonymous namespace. Однако от этой идеи отказались, чтобы сильно с сишечкой не расходиться.
Когда нам рассказывают про такой мощный инструмент и облизывают его со всех сторон, то нужно проверить, реально ли он так хорош во всех ситуациях. Сегодня поговорим про его использование в хэдэрах.
Открываем С++ Core Guidelines и видим, что там написано "не используйте анонимные пространства имен в заголовочных файлах". Эх, а так хотелось...
Это рекомендация практически полностью основана на том факте, что сущности в безымянных неймспейсах имеют внутреннее связывание. И это может привести к следующим проблемам:
1️⃣ Раздувается бинарный файл. Во всех единицах трансляции, куда включен ваш хэдэр, будут содержаться копии сущностей из анонимного пространства имен. На копии тратится память и, ну вы поняли...
2️⃣ Вы рискуете нарваться на неопределенное поведение. Рассмотрим такой пример:
namespace {
double const pi = 3.14159;
}
inline double twoPiR( double r ) { return 2.0 * pi * r; }
Правило для встроенных функций заключается в том, что их определения во всех единицах трансляции должны быть идентичными. В примере выше каждая единица перевода имеет отдельный экземпляр pi. И результирующее определении twoPiR будет выбрано рандомно из всех юнитов, поэтому в нем будет локальным адрес pi из какого-то одного юнита. Соотвественно, использование локального адреса одной единицы трансляции в другой ведет к UB.
Конечно, даже без анонимного пространства имен это было бы неопределенное поведение здесь (поскольку const означает внутреннюю линковку по умолчанию), но основной принцип сохраняется. Любое использование в заголовке чего-либо в неназванном пространстве имен (или любого объекта const, определенного в заголовке), скорее всего, вызовет неопределенное поведение. Является ли это реальной проблемой или нет, зависит, от того, как будет обработана переменная pi. Скорее всего в этом конкретном случае компилятор просто вставит значение 3.14159 в место использования. И тогда никакой зависимости от других единиц трансляции не будет. Но для более сложных объектов не стоит надеятся на такие оптимизации.
3️⃣ Внутри одной единицы трансляции все анонимные неймспейсы на самом деле являются одним пространством с уникальным для юнита именем. Это значит, если в двух заголовочниках у вас будут такие строчки:
namespace {
int x;
}
и вы подключите их в один исходник, то произойдет нарушение ODR. У вас будут два определения одной и той же сущности внутри одного пространства имен.
Сложно контролировать имена объектов и функций, которые мы располагаем в заголовочниках, чтобы они не пересекались с именами у других хэдэрах. Поэтому, если в какой-то момент вы будете вынуждены подключить два заголовочника(код которых уже где-то используется) с пересекающимися именами в своих anonymous namespace в один цппшник, то будут проблемки. Придется переименовывать какую-то половину сущностей во всем проекте, где они использовались.
До С++17 у нас особо не было универсального и удобного способа распространять код только через хэдэра. Теперь же есть inline переменные. И мы можем определять внешнесвязные сущности в хэдэрах. Это полностью решило все перечисленные здесь проблемы.
Поэтому используйте inline функции и переменные в хэдэрах и радуйтесь жизни!
Enjoy life. Stay cool.
#cpp11 #cpp17 #compiler #cppcore