Definitions and ODR (One Definition Rule)
정의(Definitions) 는 선언(declarations) 이 도입한 개체(entity)를 완전히 정의하는 선언입니다. 다음을 제외한 모든 선언은 정의입니다:
- 함수 본문이 없는 함수 선언:
int f(int); // f를 선언하지만 정의하지는 않음
- 초기화자가 없는 extern storage class specifier 를 가지는 선언 또는 language linkage 지정자(예: extern "C" )를 가지는 선언:
extern const int a; // a를 선언하지만 정의하지는 않음 extern const int b = 1; // b를 정의함
- 클래스 정의 내부에서 non-inline (since C++17) static data member 의 선언:
struct S { int n; // S::n을 정의함 static int i; // S::i를 선언하지만 정의하지는 않음 inline static int x; // S::x를 정의함 }; // S를 정의함 int S::i; // S::i를 정의함
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; }
- A typedef 선언:
typedef S S2; // S2를 선언하지만 정의하지는 않음 (S는 불완전 타입일 수 있음)
using S2 = S; // declares, but does not define S2 (S may be incomplete) |
(C++11부터) |
using N::d; // d를 선언하지만 정의하지는 않음
|
(C++17부터) |
|
(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 를 정의할 수 있습니다.
어떤 객체의 정의가 incomplete type 이나 abstract class type 의 객체를 결과로 낳는다면, 그 프로그램은 ill-formed입니다.
목차 |
단일 정의 규칙
어떤 변수, 함수, 클래스 타입, 열거형 타입 , concept (C++20부터) 또는 템플릿의 정의는 하나의 번역 단위에서 오직 하나만 허용됩니다 (이들 중 일부는 여러 선언을 가질 수 있지만, 오직 하나의 정의만 허용됩니다).
모든 비- inline 함수 또는 변수 중 odr-used (아래 참조)되는 것은 전체 프로그램(표준 라이브러리와 사용자 정의 라이브러리를 포함하여)에 정확히 하나의 정의가 반드시 존재해야 합니다. 컴파일러가 이 위반을 진단할 필요는 없지만, 이를 위반하는 프로그램의 동작은 정의되지 않습니다.
인라인 함수 또는 인라인 변수 (C++17부터) 의 경우, 해당 함수나 변수가 odr-used 되는 모든 번역 단위에서 정의가 필요합니다.
클래스의 경우, 클래스가 완전한 형태 로 사용되어야 하는 상황에서는 해당 클래스의 정의가 반드시 필요합니다.
프로그램 내에서 다음 각 항목에 대해 둘 이상의 정의가 존재할 수 있습니다: 클래스 타입, 열거형 타입, 인라인 함수 , inline variable (since C++17) , templated entity (템플릿 또는 템플릿의 멤버이지만 완전한 template specialization 은 제외), 다음의 모든 조건이 충족되는 경우에 한합니다:
- 각 정의는 서로 다른 번역 단위에 나타납니다.
|
(C++20부터) |
- 각 정의는 동일한 시퀀스의 tokens 로 구성됩니다 (일반적으로 동일한 헤더에 나타남).
- 각 정의 내에서의 이름 검색은 동일한 개체들을 발견합니다 ( overload resolution 이후), 단 다음과 같은 예외가 있습니다:
-
- 내부 연결성(internal linkage)을 가지거나 연결성이 없는 상수들은 odr-used되지 않고 모든 정의에서 동일한 값을 가지는 한 서로 다른 객체를 참조할 수 있습니다.
|
(since C++11) |
- 오버로드된 연산자, 변환 함수, 할당 및 해제 함수는 각 정의에서 동일한 함수를 참조합니다(정의 내에서 정의된 것을 참조하는 경우 제외).
- 해당 개체들은 각 정의에서 동일한 언어 링크를 가집니다(예: 포함 파일이 extern "C" 블록 내부에 있지 않은 경우).
-
const객체가 어떤 정의에서 상수 초기화 되었다면, 각 정의에서 모두 상수 초기화됩니다. - 위 규칙들은 각 정의에서 사용된 모든 기본 인수에 적용됩니다.
- 정의가 암시적으로 선언된 생성자를 가진 클래스인 경우, ODR-사용되는 모든 번역 단위는 기본 클래스와 멤버에 대해 동일한 생성자를 호출해야 합니다.
|
(since C++20) |
- 정의가 템플릿을 위한 것이라면, 이러한 모든 요구사항은 정의 지점의 이름과 인스턴스화 지점의 종속 이름 모두에 적용됩니다.
이러한 모든 요구 사항이 충족되면, 프로그램은 전체 프로그램에 정의가 단 하나만 있는 것처럼 동작합니다. 그렇지 않으면 프로그램은 형식이 잘못되었으며, 진단이 필요하지 않습니다.
참고: C에서는 타입에 대해 프로그램 전체의 ODR(One Definition Rule)이 존재하지 않으며, 서로 다른 번역 단위에서 동일한 변수의 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 가 식별자 표현식(identifier expression) 인 경우, 표현식 E 은 유일한 잠재적 결과(potential result)입니다.
- 만약 E 가 첨자 표현식(subscript expression) ( E1 [ E2 ] )이고 피연산자 중 하나가 배열인 경우, 해당 피연산자의 잠재적 결과가 집합에 포함됩니다.
- 만약 E 가 E1. E2 또는 E1. template E2 형태의 클래스 멤버 접근 표현식이고 비정적 데이터 멤버(non-static data member)를 지칭하는 경우, E1 의 잠재적 결과가 집합에 포함됩니다.
- 만약 E 가 정적 데이터 멤버(static data member)를 지칭하는 클래스 멤버 접근 표현식인 경우, 데이터 멤버를 지정하는 식별자 표현식이 집합에 포함됩니다.
- 만약 E 가 E1. * E2 또는 E1. * template E2 형태의 멤버 포인터 접근 표현식(pointer-to-member access expression)이고 두 번째 피연산자가 상수 표현식(constant expression)인 경우, E1 의 잠재적 결과가 집합에 포함됩니다.
- 만약 E 가 괄호 안의 표현식 ( ( E1 ) )인 경우, E1 의 잠재적 결과가 집합에 포함됩니다.
- 만약 E 가 glvalue 조건 표현식(conditional expression) ( E1 ? E2 : E3 , 여기서 E2 와 E3 가 glvalue)인 경우, E2 와 E3 의 잠재적 결과들의 합집합이 집합에 포함됩니다.
- 만약 E 가 쉼표 표현식(comma expression) ( E1, E2 )인 경우, E2 의 잠재적 결과가 잠재적 결과 집합에 포함됩니다.
- 그 외의 경우, 집합은 비어 있습니다.
ODR-사용 (비공식적 정의)
객체는 그 값이 (컴파일 타임 상수가 아닌 경우) 읽히거나 쓰여질 때, 주소가 취해질 때, 또는 참조가 그것에 바인딩될 때 odr-used됩니다.
참조는 사용되고 그 참조 대상이 컴파일 시간에 알려지지 않은 경우 odr-used됩니다.
함수는 함수 호출이 이루어지거나 주소가 취해질 경우 odr-used됩니다.
엔티티가 odr-used되면, 그 정의는 프로그램 어딘가에 존재해야 합니다; 이를 위반하는 것은 보통 링크 타임 오류입니다.
struct S { static const int x = 0; // 정적 데이터 멤버 // odr-used되는 경우 클래스 외부 정의가 필요함 }; const int& f(const int& r); int n = b ? (1, S::x) // S::x는 여기서 odr-used되지 않음 : f(S::x); // S::x는 여기서 odr-used됨: 정의가 필요함
ODR-사용 (정식 정의)
x
라는 변수가
잠재적으로 평가되는 표현식
expr
에 의해 명명되고, 이 표현식이
P
지점에 나타날 때,
expr
에 의해 odr-used됩니다. 단, 다음 조건 중 어느 하나라도 충족되는 경우는 제외됩니다:
-
x
는
P에서 상수 표현식에서 사용 가능한 참조입니다. -
x
는 참조가 아니며
(C++26 이전)
expr
은 표현식
E
의 잠재적 결과 집합의 원소이며, 다음 조건 중 하나를 만족합니다:
- E 가 폐기 값 표현식 이며, 여기에 lvalue-to-rvalue 변환이 적용되지 않습니다.
-
x
가
비휘발성
(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-use하지 않음 return S::x; // lvalue-to-rvalue 변환이 적용되는 표현식은 // S::x를 odr-use하지 않음 }
* this 는 odr-used됩니다 만약 this 가 잠재적으로 평가되는 표현식으로 나타날 경우 (비정적 멤버 함수 호출 표현식에서의 암시적 this 포함).
|
구조화된 바인딩 은 잠재적으로 평가되는 표현식으로 나타날 경우 ODR-사용됩니다. |
(C++17부터) |
함수는 다음과 같은 경우에 odr-used됩니다:
- 함수는 잠재적으로 평가되는 표현식 또는 변환에 의해 이름이 사용될 경우(아래 참조) odr-used됩니다.
- 가상 멤버 함수 는 순수 가상 멤버 함수가 아닌 경우 odr-used됩니다(가상 멤버 함수의 주소는 vtable을 구성하는 데 필요합니다).
- 클래스에 대한 비배치 할당 또는 해제 함수는 해당 클래스 생성자의 정의에 의해 odr-used됩니다.
- 클래스에 대한 비배치 해제 함수는 해당 클래스 소멸자의 정의에 의해, 또는 가상 소멸자 정의 지점에서의 조회에 의해 선택됨으로써 odr-used됩니다.
-
다른 클래스
U의 멤버 또는 기반 클래스인 클래스T의 대입 연산자는U의 암시적으로 정의된 복사-대입 또는 이동-대입 함수에 의해 odr-used됩니다. - 클래스의 생성자(기본 생성자 포함)는 이를 선택하는 초기화 에 의해 odr-used됩니다.
- 클래스의 소멸자는 잠재적으로 호출될 수 있는 경우 odr-used됩니다.
|
이 섹션은 불완전합니다
이유: odr-use가 차이를 만드는 모든 상황 목록 |
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 261 | C++98 |
다형성 클래스의 할당 해제 함수가
프로그램 내 관련 new/delete 표현식이 없어도 odr-used될 수 있었음 |
odr-use 사례를 생성자와
소멸자까지 확장하여 보완 |
| CWG 678 | C++98 |
개체가 서로 다른 언어 링크를 가진
정의를 가질 수 있었음 |
이 경우 동작은
정의되지 않음 |
| CWG 1472 | C++98 |
상수 표현식에 나타날 수 있는 요구사항을 만족하는
참조 변수들은 lvalue-to-rvalue 변환이 즉시 적용되더라도 odr-used되었음 |
이 경우에는
odr-used되지 않음 |
| CWG 1614 | C++98 | 순수 가상 함수의 주소를 취하는 것이 이를 odr-used함 | 함수는 odr-used되지 않음 |
| CWG 1741 | C++98 |
잠재적으로 평가되는 표현식에서 즉시 lvalue-to-rvalue
변환되는 상수 객체들이 odr-used되었음 |
이들은 odr-used되지 않음 |
| 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 One definition rule [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]