Namespaces
Variants

Definitions and ODR (One Definition Rule)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Определения являются объявлениями , которые полностью определяют сущность, введённую объявлением. Каждое объявление является определением, за исключением следующих случаев:

  • Объявление функции без тела функции:
int f(int); // объявляет, но не определяет f
extern const int a;     // объявляет, но не определяет a
extern const int b = 1; // определяет b
struct S
{
    int n;               // определяет S::n
    static int i;        // объявляет, но не определяет S::i
    inline static int x; // определяет S::x
};                       // определяет S
int S::i;                // определяет S::i
  • (устарело) Объявление статического члена данных в области видимости пространства имен, который был определен внутри класса с constexpr спецификатором:
struct S
{
    static constexpr int x = 42; // implicitly inline, defines S::x
};
constexpr int S::x; // declares S::x, not a redefinition
(since C++17)
  • Объявление имени класса (с помощью forward declaration или с использованием elaborated type specifier в другом объявлении):
struct S;             // объявляет, но не определяет S
class Y f(class T p); // объявляет, но не определяет Y и T (а также f и p)
enum Color : int; // declares, but does not define Color
(начиная с C++11)
template<typename T> // объявляет, но не определяет T
  • Объявление параметра в объявлении функции, которое не является определением:
int f(int x); // объявляет, но не определяет f и x
int f(int x)  // определяет f и x
{
    return x + a;
}
typedef S S2; // объявляет, но не определяет S2 (S может быть неполным)
using S2 = S; // declares, but does not define S2 (S may be incomplete)
(начиная с C++11)
using N::d; // объявляет, но не определяет d
  • Объявление deduction guide (не определяет никаких сущностей)
(since C++17)
  • Объявление static_assert (не определяет никаких сущностей)
  • Объявление attribute declaration (не определяет никаких сущностей)
(since C++11)
extern template
f<int, char>; // declares, but does not define f<int, char>
(начиная с C++11)
template<>
struct A<int>; // объявляет, но не определяет A<int>

Объявление asm не определяет никаких сущностей, но классифицируется как определение.

При необходимости компилятор может неявно определить default constructor , copy constructor , move constructor , copy assignment operator , move assignment operator , и destructor .

Если определение любого объекта приводит к объекту неполного типа или абстрактного типа класса , программа является некорректной.

Содержание

Правило одного определения

Только одно определение любой переменной, функции, типа класса, типа перечисления , concept (since C++20) или шаблона разрешено в одной единице трансляции (некоторые из них могут иметь несколько объявлений, но разрешено только одно определение).

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

Для встроенной функции или встроенной переменной (начиная с C++17) , определение требуется в каждой единице трансляции, где она odr-used .

Для класса определение требуется везде, где класс используется таким образом, что требуется его полное определение.

В программе может быть более одного определения для каждого из следующих: тип класса, тип перечисления, встраиваемая функция , встраиваемая переменная (since C++17) , шаблонная сущность (шаблон или член шаблона, но не полная специализация шаблона ), при условии что все следующие условия выполняются:

  • Каждое определение появляется в отдельной единице трансляции.
(начиная с C++20)
  • Каждое определение состоит из одинаковой последовательности токенов (обычно присутствует в одном и том же заголовочном файле).
  • Поиск имени внутри каждого определения находит те же сущности (после разрешения перегрузки ), за исключением случаев, когда:
  • Константы с внутренней или без компоновки могут ссылаться на разные объекты, при условии что они не являются ODR-используемыми и имеют одинаковые значения в каждом определении.
  • Лямбда-выражения не в аргументе по умолчанию или аргументе шаблона по умолчанию (since C++20) однозначно идентифицируются последовательностью токенов, используемой для их определения.
(since C++11)
  • Перегруженные операторы, включая функции преобразования, выделения и освобождения памяти, ссылаются на одну и ту же функцию из каждого определения (если не ссылаются на ту, что определена внутри определения).
  • Соответствующие сущности имеют одинаковую языковую линковку в каждом определении (например, включаемый файл не находится внутри блока extern "C" ).
  • Если const объект константно-инициализирован в любом из определений, он константно-инициализирован в каждом определении.
  • Вышеуказанные правила применяются к каждому аргументу по умолчанию, используемому в каждом определении.
  • Если определение предназначено для класса с неявно объявленным конструктором, каждая единица трансляции, где он ODR-используется, должна вызывать один и тот же конструктор для базового класса и членов.
  • Если определение предназначено для класса с оператором трёхстороннего сравнения по умолчанию, каждая единица трансляции, где он ODR-используется, должна вызывать тот же оператор сравнения для базовых классов и членов.
(since C++20)
  • Если определение является шаблоном, то все эти требования применяются как к именам в точке определения, так и к зависимым именам в точке инстанцирования.

Если все эти требования выполнены, программа ведет себя так, как если бы в ней существовало только одно определение. В противном случае программа является некорректной, диагностика не требуется.

Примечание: в языке C не существует общепрограммного правила одного определения (ODR) для типов, и даже extern-объявления одной и той же переменной в разных единицах трансляции могут иметь разные типы при условии их совместимости . В C++ исходные токены, используемые в объявлениях одного и того же типа, должны быть идентичными, как описано выше: если один .cpp файл определяет struct S { int x ; } ; , а другой .cpp файл определяет struct S { int y ; } ; , то поведение программы, связывающей их вместе, не определено. Эта проблема обычно решается с помощью безымянных пространств имён .

Наименование сущности

Переменная именуется выражением, если выражение является идентифицирующим выражением, которое обозначает её.

Функция называется выражением или преобразованием в следующих случаях:

  • Функция, имя которой появляется в виде выражения или преобразования (включая именованную функцию, перегруженный оператор, пользовательское преобразование , пользовательские формы размещения operator new , нестандартная инициализация) считается названной этим выражением, если она выбрана разрешением перегрузки, за исключением случаев, когда это неквалифицированная чисто виртуальная функция-член или указатель-на-член на чисто виртуальную функцию.
  • Функция выделения или освобождения памяти для класса считается названной выражением new , появляющимся в выражении.
  • Функция освобождения памяти для класса считается названной выражением delete , появляющимся в выражении.
  • Конструктор, выбранный для копирования или перемещения объекта, считается названным выражением или преобразованием, даже если происходит исключение копирования . Использование prvalue в некоторых контекстах не копирует и не перемещает объект, см. обязательное исключение . (начиная с C++17)

Потенциально вычисляемое выражение или преобразование odr-использует функцию, если называет её.

Выражение или преобразование, потенциально вычисляемое в контексте констант, которое ссылается на constexpr-функцию, делает её необходимой для константного вычисления , что вызывает определение функции по умолчанию или инстанцирование специализации шаблона функции, даже если выражение не вычисляется.

(since C++11)

Возможные результаты

Множество потенциальных результатов выражения E представляет собой (возможно, пустое) множество выражений-идентификаторов, которые встречаются внутри E , объединённых следующим образом:

  • Если E является выражением-идентификатором , то выражение E является его единственным потенциальным результатом.
  • Если E является индексным выражением ( E1 [ E2 ] ), где один из операндов является массивом, потенциальные результаты этого операнда включаются в множество.
  • Если E является выражением доступа к члену класса вида E1. E2 или E1. template E2 , называющим нестатический член данных, потенциальные результаты E1 включаются в множество.
  • Если E является выражением доступа к члену класса, называющим статический член данных, выражение-идентификатор, обозначающее член данных, включается в множество.
  • Если E является выражением доступа по указателю на член вида E1. * E2 или E1. * template E2 , чей второй операнд является константным выражением, потенциальные результаты E1 включаются в множество.
  • Если E является выражением в круглых скобках ( ( E1 ) ), потенциальные результаты E1 включаются в множество.
  • Если E является glvalue условным выражением ( E1 ? E2 : E3 , где E2 и E3 являются glvalue), объединение потенциальных результатов E2 и E3 включаются в множество.
  • Если E является выражением с запятой ( E1, E2 ), потенциальные результаты E2 входят в множество потенциальных результатов.
  • В противном случае множество пусто.

Использование ODR (неформальное определение)

Объект используется в соответствии с правилом ODR, если его значение читается (если только это не константа времени компиляции) или записывается, берётся его адрес или к нему привязывается ссылка.

Ссылка является odr-используемой, если она используется и её референт неизвестен на этапе компиляции,

Функция является odr-используемой, если к ней производится вызов функции или берётся её адрес.

Если сущность используется в соответствии с ODR, её определение должно присутствовать где-то в программе; нарушение этого правила обычно приводит к ошибке на этапе компоновки.

struct S
{
    static const int x = 0; // статический член данных
    // определение вне класса требуется, если он используется в odr-контексте
};
const int& f(const int& r);
int n = b ? (1, S::x) // S::x не используется в odr-контексте здесь
          : f(S::x);  // S::x используется в odr-контексте здесь: требуется определение

Использование ODR (формальное определение)

Переменная x , именуемая потенциально вычисляемым выражением expr , которое появляется в точке P , является odr-используемой выражением expr , если не выполняется ни одно из следующих условий:

  • x является ссылкой, которая может использоваться в константных выражениях в точке P .
  • x не является ссылкой и (до C++26) expr является элементом множества потенциальных результатов выражения E , и выполняется любое из следующих условий:
    • E является discarded-value выражением , и к нему не применяется преобразование lvalue-to-rvalue.
    • x является не-volatile (начиная с C++26) объектом, который может использоваться в константных выражениях в точке P и не имеет изменяемых подобъектов, и выполняется любое из следующих условий:
(начиная с C++26)
  • E имеет не-volatile-квалифицированный не-классовый тип, и к нему применяется преобразование lvalue-to-rvalue.
struct S { static const int x = 1; }; // применение lvalue-to-rvalue преобразования
                                      // к S::x дает константное выражение
int f()
{
    S::x;        // выражение с отброшенным значением не использует S::x в odr-смысле
    return S::x; // выражение, где применяется lvalue-to-rvalue преобразование
                 // не использует S::x в odr-смысле
}

* this является odr-используемым, если this появляется как потенциально вычисляемое выражение (включая неявный this в выражении вызова нестатической функции-члена).

Структурированная привязка считается odr-используемой, если она появляется в потенциально вычисляемом выражении.

(начиная с C++17)

Функция является odr-используемой в следующих случаях:

  • Функция является odr-используемой, если она названа (см. ниже) потенциально вычисляемым выражением или преобразованием.
  • Виртуальная функция-член является odr-используемой, если она не является чисто виртуальной функцией-членом (адреса виртуальных функций-членов требуются для построения vtable).
  • Функция неразмещающего выделения или освобождения памяти для класса является odr-используемой определением конструктора этого класса.
  • Функция неразмещающего освобождения памяти для класса является odr-используемой определением деструктора этого класса или выбором при поиске в точке определения виртуального деструктора.
  • Оператор присваивания в классе T , который является членом или базовым классом другого класса U , является odr-используемым неявно определенными функциями копирующего или перемещающего присваивания U .
  • Конструктор (включая конструкторы по умолчанию) для класса является odr-используемым инициализацией , которая его выбирает.
  • Деструктор для класса является odr-используемым, если он потенциально вызывается .

Отчеты о дефектах

Следующие отчеты об изменениях поведения, влияющие на дефекты, были применены задним числом к ранее опубликованным стандартам C++.

DR Применяется к Поведение в опубликованной версии Корректное поведение
CWG 261 C++98 функция освобождения для полиморфного класса
могла быть odr-использована даже при отсутствии
соответствующих выражений new или delete в программе
дополнены случаи
odr-использования для
конструкторов и деструкторов
CWG 678 C++98 сущность могла иметь определения
с различными языковыми связями
поведение является
неопределённым в этом случае
CWG 1472 C++98 ссылочные переменные, удовлетворяющие требованиям для
появления в константном выражении, были odr-использованы даже
если преобразование lvalue-to-rvalue применяется немедленно
они не являются
odr-использованными в этом случае
CWG 1614 C++98 взятие адреса чистой виртуальной функции odr-использовало её функция не является odr-использованной
CWG 1741 C++98 константные объекты, которые немедленно преобразуются
lvalue-to-rvalue в потенциально вычисляемых выражениях, были odr-использованы
они не являются odr-использованными
CWG 1926 C++98 выражения индексации массива не распространяли потенциальные результаты они распространяют
CWG 2242 C++98 было неясно, нарушает ли const объект, который только
частично константно-инициализирован в своих определениях, ODR
ODR не нарушен; объект является
константно-инициализированным в этом случае
CWG 2300 C++11 лямбда-выражения в различных единицах трансляции
никогда не могли иметь одинаковый тип замыкания
тип замыкания может быть
одинаковым согласно правилу одного определения
CWG 2353 C++98 статический член данных не был потенциальным результатом
выражения доступа к члену, обращающегося к нему
является
CWG 2433 C++14 шаблон переменной не мог иметь
множественные определения в программе
может

Ссылки

  • Стандарт C++23 (ISO/IEC 14882:2024):
  • 6.3 Правило одного определения [basic.def.odr]
  • Стандарт C++20 (ISO/IEC 14882:2020):
  • 6.3 Правило одного определения [basic.def.odr]
  • Стандарт C++17 (ISO/IEC 14882:2017):
  • 6.2 Правило одного определения [basic.def.odr]
  • Стандарт C++14 (ISO/IEC 14882:2014):
  • 3.2 Правило одного определения [basic.def.odr]
  • Стандарт C++11 (ISO/IEC 14882:2011):
  • 3.2 Правило одного определения [basic.def.odr]
  • Стандарт C++03 (ISO/IEC 14882:2003):
  • 3.2 Правило одного определения [basic.def.odr]
  • Стандарт C++98 (ISO/IEC 14882:1998):
  • 3.2 Правило одного определения [basic.def.odr]