Правила ODR:
- В пределах любой единицы трансляции шаблон, тип данных, функция или объект не могут иметь более одного определения, но могут иметь неограниченное число объявлений.
- В пределах программы (совокупности всех единиц трансляции) объект или не-inline функция не могут иметь более одного определения; если объект или функция используются, у каждого из них должно быть строго по единственному определению.
- Типы, шаблоны и inline-функции (то есть те сущности, определение которых полностью или частично совмещается с их объявлением) могут определяться в более чем одной единице трансляции, но для каждой такой сущности все её определения должны быть идентичны.
ODR можно легально нарушить с помощью ключевого слова inline
.
Связывание (linkage)
Без связывания
Символ доступен только из блока, в котором был объявлен.
Внутреннее связывание (internal linkage)
Символ доступен из всех блоков в данной единице трансляции.
Внешнее связывание (external linkage)
Символ доступен из всех блоков в других единицах трансляции.
Модульное связывание (module linkage)
Символ доступен из всех блоков только данного модуля или в других единицах трансляции того же самого именованного модуля.
Символы, определенные в пространстве имен, имеют модульное связывание, если их объявление находится в именованном модуле и не было экспортировано (exported
), и если они не имеют внутреннего связывания.
// TODO: не уверен в этом пункте. нужно изучить этот вопрос.
Какие ключевые слова влияют на связывание?
- Использование
static
в глобальном пространстве имен дает символу внутреннее связывание. - Использование
extern
дает внешнее связывание.
Компилятор по умолчанию дает символам следующие связывания:
- Non-const глобальные переменные - внешнее связывание
- Const глобальные переменные - внутреннее связывание
- Функции - внешнее связывание
- Объявление в анонимном пространстве имен - внутреннее связывание.
То есть, например, константы в файлах .h
, по умолчанию будут продублированы в каждой единице трансляции при подключении через #include
. То есть каждый файл .cpp
будет иметь свою собственную константу.
Это может негативно сказаться на скорости компиляции при изменении этой константы, так как перекомпилироваться будут все единицы трансляции, в которых эта константа присутствовала.
Чтобы этого избежать, можно пометить константы в файле .h
как extern
, затем инициализировать их единожды в каком-нибудь .cpp
, затем подключать этот хедер где угодно.
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants {
extern const double pi;
extern const double avogadro;
}
#endif
// constants.cpp
#include "constants.h"
namespace constants {
// keyword extern can be omitted
extern const double pi { 3.14159 };
extern const double avogadro { 6.0221413e23 };
}
Однако у этого метода есть недостаток. Эти константы теперь могут считаться константами времени компиляции только в файле, в котором они фактически определены (constants.cpp
), а не где-либо еще.
Ключевое слово inline
Ключевое слово inline
говорит линкеру игнорировать то, что в нескольких .cpp
файлах встречаются одинаковые определения. Линкер будет брать рандомное, предполагая, что все они равны. Если они не равны, поведение не определено.
Также слово inline
может объявлять inline-переменную и влиять на связывание (linkage) этой переменной начиная со стандарта C++17, если она глобальная, дополняя правила связывания переменных:
static
- внутреннее связываниеextern
- внешнее связываниеinline
- внешнее связываниеconst
&&inline
- внешнее связываниеconst
&& !inline
- внутреннее связывание
Предыдущий пример можно переписать следующим образом:
// constants.h
#ifndef CONSTANTS_H
#define CONSTANTS_H
namespace constants {
inline constexpr double pi { 3.14159 };
inline constexpr double avogadro { 6.0221413e23 };
}
#endif
Также можно неформально сказать, что все шаблонные классы/методы/функции являются inline автоматически.
Constexpr-функции также неявно являются inline.
Заимствования: