Одно правило определения
Правило одного определения ( ODR ) — это важное правило C++ языка программирования , которое предписывает, что классы/структуры и невстроенные функции не могут иметь более одного определения во всей программе и шаблоне, а типы не могут иметь более одного определения по единице перевода. . Он определен в стандарте ISO C++ ( ISO/IEC 14882 ) 2003, раздел 3.2. Некоторые другие языки программирования имеют схожие, но по-разному определенные правила, направленные на достижение той же цели.
Краткое содержание
[ редактировать ]Короче говоря, в ODR говорится следующее:
- В любой единице перевода шаблон , тип , функция или объект может иметь не более одного определения. Некоторые из них могут иметь любое количество объявлений. Определение предоставляет экземпляр.
- Во всей программе объект или невстроенная функция не может иметь более одного определения; если используется объект или функция, у него должно быть ровно одно определение. Вы можете объявить объект или функцию, которые никогда не используются, и в этом случае вам не нужно предоставлять определение. Ни в коем случае не может быть более одного определения.
- Некоторые вещи, такие как типы, шаблоны и внешние встроенные функции, могут быть определены более чем в одной единице перевода. Для данной сущности каждое определение должно иметь одинаковую последовательность токенов . Невнешние объекты и функции в разных единицах трансляции являются разными сущностями, даже если их имена и типы одинаковы.
Некоторые нарушения ОДР должны быть диагностированы составителем . Другие нарушения, особенно те, которые охватывают единицы трансляции, диагностировать не требуется. [ 1 ]
Примеры
[ редактировать ]Как правило, единица перевода должна содержать не более одного определения любого типа класса. В этом примере два определения типа класса C встречаются в одной и той же единице перевода . Обычно это происходит, если файл заголовка включен дважды в один и тот же исходный файл без соответствующих средств защиты заголовка .
class C {}; // first definition of C
class C {}; // error, second definition of C
Далее формирование указателя на S или определение функции, принимающей ссылку на S, являются примерами допустимых конструкций, поскольку они не требуют, чтобы тип S был полным . Поэтому определение не требуется. [ 2 ]
Определение объекта типа S, функции, принимающей аргумент типа S, или использование S в выражении sizeof — это примеры контекстов, в которых S должен быть полным и, следовательно, требует определения. [ 2 ]
struct S; // declaration of S
S * p; // ok, no definition required
void f(S&); // ok, no definition required
void f(S*); // ok, no definition required
S f(); // ok, no definition required - this is a function declaration only!
S s; // error, definition required
sizeof(S); // error, definition required
Более одного определения
[ редактировать ]В некоторых случаях может существовать более одного определения типа или шаблона. Программа, состоящая из нескольких файлов заголовков и исходных файлов, обычно имеет более одного определения типа, но не более одного определения на единицу перевода.
Если программа содержит более одного определения типа, то каждое определение должно быть эквивалентным. [ 3 ]
Определения статических константных членов данных
[ редактировать ]В достандартном C++ все статические члены данных требовали определения вне своего класса. Однако в процессе стандартизации C++ было решено снять это требование для статических константных целочисленных членов. Цель заключалась в том, чтобы разрешить такие виды использования, как:
struct C {
static const int N = 10;
};
char data[C::N]; // N "used" without out-of-class definition
без определения области пространства имен для N
.
Тем не менее, формулировка стандарта C++ 1998 года по-прежнему требовала определения, если элемент использовался в программе. [ 4 ] Это включало появление члена где угодно, кроме операнда sizeof или typeid , что фактически делало приведенное выше некорректным. [ 5 ]
Это было идентифицировано как дефект, и формулировка была скорректирована, чтобы позволить такому члену появляться везде, где требуется постоянное выражение , без необходимости определения вне класса. Сюда входят границы массива , выражения регистра , статические инициализаторы членов и нетиповые аргументы шаблона . [ 6 ]
struct C {
static const int N = 10;
static const int U = N; // Legal per C++03
};
char data[C::N]; // Legal per C++03
template<int> struct D;
template<> struct D<C::N> {}; // Legal per C++03
Однако использование статического константного целого члена где угодно, кроме случаев, когда требуется целочисленное константное выражение, требует определения: [ 7 ]
struct C {
static const int N = 10;
};
int main() {
int i = C::N; // Ill-formed in C++03. Definition of C::N required.
}
Это требование было смягчено в более позднем стандарте C++11 . [ 7 ]
Пример, показывающий неожиданные побочные эффекты
[ редактировать ]Нам понадобится 4 файла: "odr.h", "main.cpp", "odr1.cpp", "odr2.cpp"
Аббревиатура «odr» здесь является сокращением от «Одно правило определения».
одр.х:
// abstract base class
class CBase {
public:
virtual void xxx() = 0;
virtual ~CBase() = default;
};
extern CBase *odr1_create();
extern CBase *odr2_create();
main.cpp
#include "odr.h"
int main(int argc, char **argv)
{
CBase *o1 = odr1_create();
CBase *o2 = odr2_create();
o1->xxx();
o2->xxx();
}
odr1.cpp
#include <stdio.h>
#include "odr.h"
class CDummy : public CBase {
public:
void xxx() override {
printf("odr ONE dummy: Hello\n");
}
};
CBase *odr1_create() {
return new CDummy();
}
odr2.cpp
#include <stdio.h>
#include "odr.h"
class CDummy : public CBase {
public:
void xxx() override {
printf("odr TWO dummy: World\n");
}
};
CBase *odr2_create() {
return new CDummy();
}
В оболочке Linux, чтобы опробовать, скомпилируйте с помощью:
g++ -c odr1.cpp g++ -c odr2.cpp g++ -c main.cpp g++ -o odr main.o odr1.o odr2.o
В Windows Visual Studio «Командная строка инструментов сборки» скомпилируйте с помощью:
cl /c main.cpp cl /c odr1.cpp cl /c odr2.cpp cl /Feodr.exe main.obj odr1.obj odr2.obj
При выполнении ожидаемый результат:
odr ONE dummy: Hello odr TWO dummy: World
Но вы, скорее всего, получите:
odr ONE dummy: Hello odr ONE dummy: Hello
Проблема в том, что компоновщик C++ должен выяснить, как построить таблицу виртуальных методов для (двух разных) классов «CDummy», и это работает только в том случае, если имена классов различаются.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §3.2 Одно правило определения [basic.def.odr], параграф. 3
- ^ Перейти обратно: а б ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §3.2 Одно правило определения [basic.def.odr], параграф. 4
- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §3.2 Одно правило определения [basic.def.odr], параграф. 5
- ^ ИСО / МЭК (1998). ISO/IEC 14882:1998(E): Языки программирования – C++ §9.4.2 Статические элементы данных [class.static.data], параграф. 4
- ^ ИСО / МЭК (1998). ISO/IEC 14882:1998(E): Языки программирования – C++ §3.2 Одно правило определения [basic.def.odr], параграф. 2
- ^ ИСО / МЭК (2003). ISO/IEC 14882:2003(E): Языки программирования – C++ §5.19 Константные выражения [expr.const], параграф. 1
- ^ Перейти обратно: а б «Когда требуется определение статического элемента данных?» . РГ21 . Проверено 15 апреля 2009 г.