Spec-Zone.ru › C++

Определения и правило одной дефиниции (ОДР)

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

  • Объявление функции без тела функции:
int f(int); // declares, but doesn't define f
  • Любое объявление со extern спецификатором класса хранения или со спецификатором связи языков программирования (таким как extern "C") без инициализатора:
extern const int a;     // declares, but doesn't define a
extern const int b = 1; // defines b
  • Объявление не-встроенного(с C++17) статического члена данных внутри определения класса:
struct S
{
    int n;               // defines S::n
    static int i;        // declares, but doesn't define S::i
    inline static int x; // defines S::x
};                       // defines S
 
int S::i;                // defines 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
(с C++17)
  • Объявление имени класса (с помощью предварительного объявления или с помощью спецификатора уточнённого типа в другом объявлении):
struct S;             // declares, but doesn't define S
 
class Y f(class T p); // declares, but doesn't define Y and T (and also f and p)
  • Неявное объявление перечисления:
enum Color : int; // declares, but doesn't define Color
(с C++11)
  • Объявление параметра шаблона:
template<typename T> // declares, but doesn't define T
  • Объявление параметра в объявлении функции, которое не является определением:
int f(int x); // declares, but doesn't define f and x
 
int f(int x)  // defines f and x
{
    return x + a;
}
  • Объявление typedef:
typedef S S2; // declares, but doesn't define S2 (S may be incomplete)
  • Объявление псевдонима типа:
using S2 = S; // declares, but doesn't define S2 (S may be incomplete)
(с C++11)
  • Объявление using:
using N::d; // declares, but doesn't define d
  • Объявление руководства по выводу (не определяет какие-либо сущности)
(с C++17)
  • Объявление static_assert (не определяет какие-либо сущности)
  • Объявление атрибута (не определяет какие-либо сущности)
(с C++11)
  • Пустое объявление (не определяет какие-либо сущности)
  • Директива using (не определяет какие-либо сущности)
  • Объявление явной инстанциации (шаблон «extern»):
extern template
f<int, char>; // declares, but doesn't define f<int, char>
(с C++11)
  • Явная специализация, объявление которой не является определением:
template<>
struct A<int>; // declares, but doesn't define A<int>

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

При необходимости компилятор может неявно определить конструктор по умолчанию, копирующий конструктор, перемещающий конструктор, копирующий оператор присваивания, перемещающий оператор присваивания и деструктор.

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

Правило одной дефиниции

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

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

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

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

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

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

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

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

Использование в соответствии с правилом одной дефиниции

Неформально,

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

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

struct S
{
    static const int x = 0; // static data member
    // a definition outside of class is required if it is odr-used
};
 
const int& f(const int& r);
 
int n = b ? (1, S::x) // S::x is not odr-used here
          : f(S::x);  // S::x is odr-used here: a definition is required

Формально,

1) переменная x в выражение, потенциально подлежащее вычислению ex используется в соответствии с правилом ODR, если не выполняются оба следующих условия:
  • применение преобразования lvalue в rvalue к x даёт константное выражение, не вызывающее нетривиальных функций
  • либо x не является объектом (то есть, x является ссылкой), или, если x является объектом, он является одним из возможных результатов более сложного выражения e, где это более сложное выражение является либо выражением с отбрасываемым значением, либо к нему применено преобразование lvalue в rvalue
struct S { static const int x = 1; }; // applying lvalue-to-rvalue conversion
                                      // to S::x yields a constant expression
 
int f()
{ 
    S::x;        // discarded-value expression does not odr-use S::x
 
    return S::x; // expression where lvalue-to-rvalue conversion
                 // applies does not odr-use S::x
}
2) *this используется в соответствии с правилом ODR, если this появляется как выражение, потенциально подлежащее вычислению (включая неявное this в выражении вызова нестатического члена класса)
3) Структурированная ссылка структурированной связи используется в соответствии с правилом ODR, если она появляется как выражение, потенциально подлежащее вычислению. (с C++17)

Набор возможных результатов выражения 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 находятся в наборе возможных результатов.
  • В противном случае набор пуст.
struct S
{
    static const int a = 1;
    static const int b = 2;
};
 
int f(bool x)
{
    return x ? S::a : S::b;
    // x is a part of the subexpression "x" (to the left of ?),
    // which applies lvalue-to-rvalue conversion, but applying that conversion to x
    // does not yield a constant expression, so x is odr-used
    // S::a and S::b are lvalues, and carry over as "potential results"
    // to the result of the glvalue conditional
    // That result is then subject to lvalue-to-rvalue conversion requested
    // to copy-initialize the return value, therefore S::a and S::b are not odr-used
}
4) Функция используется в соответствии с правилом ODR в следующих случаях:
  • Функция используется в соответствии с правилом ODR, если она названа (см. ниже) выражением или преобразованием, потенциально подлежащим вычислению.
  • Виртуальный член-функция виртуальный член-функции используется в соответствии с правилом ODR, если это не чистая виртуальная член-функция (адреса виртуальных член-функций необходимы для построения таблицы виртуальных функций).
  • Функция не размещенного выделения или освобождения памяти для класса используется в соответствии с правилом ODR определением конструктора этого класса.
  • Функция не размещенного освобождения памяти для класса используется в соответствии с правилом ODR определением деструктора этого класса или при выборе по поиску в момент определения виртуального деструктора.
  • Оператор присваивания в классе T, который является членом или базовым классом другого класса U, используется в соответствии с правилом ODR неявно определёнными операторами копирующего или перемещающего присваивания U.
  • Конструктор (включая конструкторы по умолчанию) для класса используется в соответствии с правилом ODR инициализацией, которая его выбирает.
  • Деструктор для класса используется в соответствии с правилом ODR, если он потенциально вызывается.

Имя функции

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

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

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

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

(с C++11)

Отчёты об ошибках

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

DR Применён к Поведение, как опубликовано Правильное поведение
CWG 261 C++98 Функция освобождения памяти для полиморфного класса
может быть использована в соответствии с ODR,
даже если в программе нет соответствующих
выражений new или delete
дополнение
случаев использования ODR
для охвата конструкторов
и деструкторов
CWG 678 C++98 сущность может иметь определения
с различными языковыми связями
поведение в этом случае
неопределённо
CWG 1472 C++98 переменные-ссылки, удовлетворяющие требованиям для
появления в константном выражении, использовались в соответствии
с правилом ODR, даже если преобразование lvalue в 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:2023):
    • 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]

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/definition

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API