C++
Одно правило определения (ODR)
Поиск…
Умножённая функция
Важнейшим следствием правила Единого определения является то, что не-встроенные функции с внешней связью следует определять только один раз в программе, хотя они могут быть объявлены несколько раз. Поэтому такие функции не должны определяться в заголовках, поскольку заголовок может быть включен несколько раз из разных единиц перевода.
foo.h
:
#ifndef FOO_H
#define FOO_H
#include <iostream>
void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp
:
#include "foo.h"
void bar() { std:: cout << "bar"; }
main.cpp
:
#include "foo.h"
int main() {
foo();
bar();
}
В этой программе функция foo
определена в заголовке foo.h
, который включается дважды: один раз из foo.cpp
и один раз из main.cpp
. Поэтому каждая единица перевода содержит свое собственное определение foo
. Обратите внимание, что включение foo.h
в foo.h
не мешает этому, поскольку foo.cpp
и main.cpp
оба отдельно включают foo.h
Наиболее вероятным результатом попытки создания этой программы является ошибка времени соединения, определяющая foo
как многократно определенную.
Чтобы избежать таких ошибок, следует объявлять функции в заголовках и определять их в соответствующих файлах .cpp
, за некоторыми исключениями (см. Другие примеры).
Встроенные функции
Функция, объявленная в inline
может быть определена в нескольких единицах перевода, при условии, что все определения идентичны. Он также должен быть определен в каждой единицы перевода, в которой он используется. Поэтому встроенные функции должны быть определены в заголовках, и нет необходимости упоминать их в файле реализации.
Программа будет вести себя так, как если бы было одно определение функции.
foo.h
:
#ifndef FOO_H
#define FOO_H
#include <iostream>
inline void foo() { std::cout << "foo"; }
void bar();
#endif
foo.cpp
:
#include "foo.h"
void bar() {
// more complicated definition
}
main.cpp
:
#include "foo.h"
int main() {
foo();
bar();
}
В этом примере более простая функция foo
определена внутри строки в файле заголовка, тогда как более сложная функциональная bar
не является встроенной и определена в файле реализации. Оба foo.cpp
перевода foo.cpp
и main.cpp
содержат определения foo
, но эта программа хорошо сформирована, поскольку foo
является встроенным.
Функция, определенная в определении класса (которая может быть функцией-членом или функцией друга), неявно встроена. Поэтому, если класс определен в заголовке, функции-члены класса могут быть определены в определении класса, хотя определения могут быть включены в несколько единиц перевода:
// in foo.h
class Foo {
void bar() { std::cout << "bar"; }
void baz();
};
// in foo.cpp
void Foo::baz() {
// definition
}
Функция Foo::baz
определяется вне строки, поэтому она не является встроенной функцией и не должна определяться в заголовке.
Нарушение ODR с помощью разрешения перегрузки
Даже при идентичных токенах для встроенных функций ODR может быть нарушен, если поиск имен не относится к одному и тому же объекту. рассмотрим func
в следующем:
header.h
void overloaded(int); inline void func() { overloaded('*'); }
foo.cpp
#include "header.h" void foo() { func(); // `overloaded` refers to `void overloaded(int)` }
bar.cpp
void overloaded(char); // can come from other include #include "header.h" void bar() { func(); // `overloaded` refers to `void overloaded(char)` }
У нас есть нарушение ODR, поскольку overloaded
относится к различным объектам в зависимости от единицы перевода.