const 객체를 변경하는 것이 정의되지 않은 동작인 경우 생성자와 소멸자는 쓰기 액세스로 어떻게 작동합니까?
-
20-09-2019 - |
문제
C++ 표준에서는 원래 선언된 개체를 수정한다고 말합니다. const
정의되지 않은 동작입니다.그러면 생성자와 소멸자는 어떻게 작동합니까?
class Class {
public:
Class() { Change(); }
~Class() { Change(); }
void Change() { data = 0; }
private:
int data;
};
//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior
여기서 생성자와 소멸자는 호출 코드와 정확히 동일한 작업을 수행하지만 개체를 변경할 수 있으며 호출자는 허용되지 않습니다. 정의되지 않은 동작이 발생합니다.
구현 및 표준에 따라 어떻게 작동해야 합니까?
해결책
표준에서는 생성자와 소멸자가 다음을 처리하도록 명시적으로 허용합니다. const
사물.12.1/4 "생성자"에서:
생성자를 호출할 수 있습니다.
const
,volatile
또는const volatile
물체....const
그리고volatile
의미론(7.1.5.1)은 생성 중인 객체에 적용되지 않습니다.이러한 의미 체계는 가장 많이 파생된 개체(1.8)에 대한 생성자가 끝난 후에만 적용됩니다.
그리고 12.4/2 "소멸자":
소멸자는 다음과 같이 호출될 수 있습니다.
const
,volatile
또는const volatile
물체....const
그리고volatile
의미론(7.1.5.1)은 소멸 중인 객체에 적용되지 않습니다.가장 많이 파생된 개체(1.8)에 대한 소멸자가 시작되면 이러한 의미 체계는 더 이상 적용되지 않습니다.
배경으로 Stroustrup은 "Design and Evolution of C++"(13.3.2 Refinement of the Defintion of C++)에서 다음과 같이 말합니다. const
):
전부는 아니지만 일부는 보장하기 위해
const
객체가 읽기 전용 메모리(ROM)에 배치될 수 있기 때문에 생성자가 있는 객체(즉, 런타임 초기화가 필요함)가 ROM에 배치될 수 없다는 규칙을 채택했습니다.const
객체는 할 수 있습니다....
선언된 객체
const
생성자가 완료된 후 소멸자가 시작될 때까지 변경 불가능한 것으로 간주됩니다.해당 지점 사이의 개체에 대한 쓰기 결과는 정의되지 않은 것으로 간주됩니다.원래 디자인할 때
const
, 나는 이상적이라고 주장했던 것을 기억합니다const
생성자가 실행될 때까지 쓰기 가능한 객체이며, 일부 하드웨어 마법에 의해 읽기 전용이 되고, 마지막으로 소멸자에 진입하면 다시 쓰기 가능해집니다.실제로 이런 방식으로 작동하는 태그된 아키텍처를 상상할 수 있습니다.이러한 구현은 누군가가 정의된 객체에 쓸 수 있는 경우 런타임 오류를 발생시킵니다.const
.반면에 누군가는 정의되지 않은 객체에 쓸 수 있습니다.const
그것은 다음과 같이 전달되었습니다.const
참조 또는 포인터.두 경우 모두 사용자는 이를 버려야 합니다.const
첫 번째.이 견해가 암시하는 바는const
원래 정의된 객체의 경우const
그런 다음 거기에 쓰는 것은 기껏해야 정의되지 않은 반면, 원래 정의되지 않은 객체에 동일한 작업을 수행하는 것은const
합법적이고 잘 정의되어 있습니다.이러한 규칙의 개선을 통해 다음의 의미는 다음과 같습니다.
const
유형에 생성자가 있는지 여부에 의존하지 않습니다.원칙적으로는 모두 그렇습니다.선언된 모든 객체const
이제 초기 값을 받은 후 변경되지 않도록 ROM에 배치하고, 코드 세그먼트에 배치하고, 액세스 제어 등으로 보호할 수 있습니다.그러나 현재 시스템은 일반적으로 모든 것을 보호할 수 없기 때문에 그러한 보호가 필요하지 않습니다.const
모든 형태의 부패로부터.
다른 팁
Jerry Coffin이 말한 내용을 자세히 설명하면 다음과 같습니다.표준에서는 객체의 수명 동안 해당 액세스가 발생하는 경우에만 const 객체에 대한 액세스를 정의되지 않은 상태로 만듭니다.
7.1.5.1/4:
변경 가능으로 선언된 클래스 멤버(7.1.1)를 수정할 수 있다는 점을 제외하고, 수명(3.8) 동안 const 객체를 수정하려고 하면 정의되지 않은 동작이 발생합니다.
객체의 수명은 생성자가 완료된 후에만 시작됩니다.
3.8/1:
T 유형 객체의 수명은 다음과 같은 경우에 시작됩니다.
- 유형 T에 대해 적절한 정렬 및 크기를 갖춘 저장소가 확보되었습니다.
- T가 중요한 생성자를 포함하는 클래스 유형(12.1)인 경우 생성자 호출이 완료된 것입니다.
표준은 실제로 구현이 어떻게 작동하는지에 대해 많이 말하지 않지만 기본 아이디어는 매우 간단합니다. const
에 적용됩니다 물체, 객체가 저장되는 메모리에 (반드시) (반드시)가 아닙니다. CTOR는 객체를 만드는 것의 일부이므로 CTOR가 돌아올 때까지 (때때로) 물체가 아닙니다. 마찬가지로, DTOR는 물체를 파괴하는 데 참여하기 때문에 더 이상 완전한 객체에서도 작동하지 않습니다.
표준을 무시하면 잘못된 행동으로 이어질 수있는 방법이 있습니다. 다음과 같은 상황을 고려하십시오.
class Value
{
int value;
public:
value(int initial_value = 0)
: value(initial_value)
{
}
void set(int new_value)
{
value = new_value;
}
int get() const
{
return value;
}
}
void cheat(const Value &v);
int doit()
{
const Value v(5);
cheat(v);
return v.get();
}
최적화되면 컴파일러는 V가 const이라는 것을 알고있어서 호출을 대체 할 수 있습니다. v.get()
~와 함께 5
.
그러나 다른 번역 단위에서 당신은 정의했습니다. cheat()
이와 같이:
void cheat(const Value &cv)
{
Value &v = const_cast<Value &>(cv);
v.set(v.get() + 2);
}
따라서 대부분의 플랫폼에서는 이것이 실행되지만 최적화가하는 일에 따라 동작이 바뀔 수 있습니다.
사용자 정의 유형의 콘트 스네스는 내장 유형의 구성과 다릅니다. 사용자 정의 유형과 함께 사용될 때의 콘트니스는 "논리적 콘트 스네스"라고합니다. 컴파일러는 "const"라고 선언 한 멤버 기능 만 Const 객체 (또는 포인터 또는 참조)에서 호출 될 수 있도록 강요합니다. 컴파일러는 일부 읽기 전용 메모리 영역에서 객체를 할당 할 수 없습니다. 비정규 멤버 기능은 객체의 상태를 수정할 수 있어야합니다. Const 멤버 변수가 선언 될 때 회원 함수는 할 수 있어야합니다. mutable
).
내장 유형의 경우 컴파일러가 객체를 읽기 전용 메모리로 할당 할 수 있다고 생각합니다 (플랫폼이 그러한 것을 지원하는 경우). 따라서 const를 버리고 변수를 수정하면 런타임 메모리 보호 결함이 발생할 수 있습니다.