Что такое одно правило определения в C++?

Что именно говорит одно определение в C++? Единственный надежный случай, который я могу найти, это язык программирования C++, 3-й. изд., с. 9.2.3. Есть ли официальное определение правила, кроме этого?

2 ответа

Решение

Правда в стандарте (3.2 Одно определение правила):

Ни одна единица перевода не должна содержать более одного определения любой переменной, функции, типа класса, типа перечисления или шаблона.

[...]

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или объекта, которая используется в этой программе; Диагностика не требуется. Определение может явным образом появиться в программе, оно может быть найдено в стандартной или пользовательской библиотеке или (при необходимости) неявно определено (см. 12.1, 12.4 и 12.8). Встроенная функция должна быть определена в каждой единице перевода, в которой она используется.

«Правило одного определения» — это подраздел [basic.def.odr] стандарта C++. Этот подпункт содержит ряд правил. Я суммирую самые важные из них.

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

В разделах ниже это описано более подробно.

Только одно определение на единицу перевода [1]

Первое и самое основное правило — [basic.def.odr] p2:

Ни одна единица перевода не должна содержать более одного определения любого определяемого элемента .

Например, это означает, что вы не можете написать следующее:

      void foo() {} // OK
void foo() {} // error, more than one definition of foo in the same TU

На практике компилятор выдаст ошибку, сообщающую вам «переопределение foo».


[1] Единица перевода — это, по сути, один исходный файл C++. Заголовок не является единицей перевода, он входит в состав единиц перевода.

Только одно определение для каждой программы, за исключением шаблонов иinline

Более того, [basic.def.odr] p14 регулирует то, что происходит между несколькими единицами перевода:

Для любого определяемого элемента с определениями в нескольких единицах перевода:

  • еслиDявляется невстроенной нешаблонной функцией или переменной, или
  • если определения в разных единицах перевода не удовлетворяют следующим требованиям,

программа плохо сформирована; [...]

Например, это означает, что у вас не может быть двух определений.void foo() {}в разных исходных файлах. На практике это приведет к ошибке компоновщика, сообщающей вам о «несколько определениях foo».

Шаблоны и встроенные функции являются особенными, поскольку их можно определить в нескольких единицах перевода, если эти определения соответствуют строгим правилам. По сути, определения должны быть эквивалентными. Например:

      // a.cpp
inline int bar() { return 0; }
// b.cpp
inline int bar() { return 0; } // OK
// c.cpp
inline int bar() { return 1; } // ill-formed, no diagnostic required

Обратите внимание, что это правило очень важно для заголовков.#includeпо существу копирует/вставляет код в единицы перевода, поэтому определение чего-либо в заголовке рискует нарушить это правило.

Все, что используется odr [2], должно быть определено

В-третьих, он также определяет так называемое . Интуитивно, означает, что вы используете некоторую конструкцию таким образом, чтобы ее определение существовало. Например:

      int foo(); // declaration, not a definition

using foo_type = decltype(foo); // decltype (unevaluated operand) is not odr-use.
int x = foo(); // calling a function is odr-use

Использование чего имеет последствия, указанные в [basic.def.odr] стр.11:

Каждая программа должна содержать по крайней мере одно определение каждой функции или переменной, которая odr-useиспользуется-либо odrиспользование odr в этой программе за пределами отброшенного оператора ; не требуется диагностика.

На практике, если вы используете функцию, и она нигде не определена, вы получаете ошибку компоновщика, сообщающую: «нет определения foo».


[2] Первоначально этот термин назывался просто «использование»; В C++11 введен термин «odr-use», чтобы сделать его более формальным.

Другие вопросы по тегам