빈 기본 최적화 가 훌륭합니다. 그러나 다음과 같은 제한 사항이 있습니다.
비어있는 기본 클래스 중 하나가 첫 번째 비 정적 데이터 멤버의 유형이거나 해당 유형 인 경우 비어있는 기본 최적화는 금지됩니다. 동일한 유형의 두 기본 서브 오브젝트는 오브젝트 표현 내에서 다른 주소를 가져야하기 때문입니다. 가장 많이 파생 된 유형의
이 제한을 설명하려면 다음 코드를 고려하십시오. 는 static_assert
실패합니다. 반면, 변화 중 하나 Foo
또는 Bar
대신 상속에 Base2
오류를 피하기합니다 :
#include <cstddef>
struct Base {};
struct Base2 {};
struct Foo : Base {};
struct Bar : Base {
Foo foo;
};
static_assert(offsetof(Bar,foo)==0,"Error!");
이 동작을 완전히 이해합니다. 내가 이해 하지 못하는 것은 이 특정한 행동 이 존재하는 이유 입니다. 그것은 명백한 이유가 아니라 감독에 의한 것이 아니기 때문에 분명한 이유로 추가되었습니다. 이에 대한 근거는 무엇입니까?
특히 두 기본 하위 오브젝트에 다른 주소가 필요한 이유는 무엇입니까? 위 Bar
의 유형은 유형이며 foo
해당 유형의 멤버 변수입니다. 왜 Bar
기본 클래스의 유형이 기본 클래스의 유형 foo
인지 또는 그 반대인지는 알 수 없습니다.
실제로 다른 상황에서도 필요하므로 인스턴스가 포함 &foo
된 Bar
인스턴스 의 주소와 동일한 것으로 예상 됩니다 (1) . 결국, 나는 virtual
상속으로 공상을하지 않고 기본 클래스는 비어 있으며, 컴파일 Base2
은이 특별한 경우에 아무것도 깨지지 않는다는 것을 보여줍니다.
그러나 분명히이 추론은 어쨌든 부정확하며,이 제한이 필요한 다른 상황이 있습니다.
대답은 C ++ 11 이상이어야합니다 (현재 C ++ 17을 사용하고 있습니다).
(1) 참고 : EBO는 C ++ 11에서 업그레이드되었으며 특히 StandardLayoutType
s의 경우 필수가되었습니다 ( Bar
위의 a는 아닙니다 StandardLayoutType
).
답변
좋아, 그것은 항상 틀린 것처럼 보인다. 모든 예제에는 기본 객체에 대한 vtable이 있어야하므로 빈 기본 최적화가 시작되지 않습니다. 고유 한 주소가 일반적으로 좋은 이유에 대한 몇 가지 흥미로운 예를 제공한다고 생각하기 때문에 예제를 그대로 두겠습니다.
이 전체에 대해 더 깊이 연구 한 결과, 첫 번째 멤버가 빈 기본 클래스와 동일한 유형일 때 빈 기본 클래스 최적화를 사용하지 않을 기술적 이유는 없습니다. 이것은 현재 C ++ 객체 모델의 속성 일뿐입니다.
그러나 C ++ 20에는 [[no_unique_address]]
정적이 아닌 데이터 멤버가 고유 한 주소가 필요하지 않을 수도 있음을 컴파일러에 알려주 는 새로운 속성 이 있습니다 (기술적으로는 잠재적으로 겹칠 수 있음 [intro.object] / 7 ).
이것은 (강조 광산)
비 정적 데이터 멤버는 다른 비 정적 데이터 멤버 또는 기본 클래스 의 주소를 공유 할 수 있습니다. […]
따라서 첫 번째 데이터 멤버에게 속성을 제공하여 빈 기본 클래스 최적화를 “재 활성화”할 수 있습니다 [[no_unique_address]]
. 여기 에 (그리고 내가 생각할 수있는 다른 모든 경우) 어떻게 작동하는지 보여주는 예를 여기에 추가했습니다 .
이것을 통해 문제의 잘못된 예
빈 클래스에는 가상 메서드가 없을 수 있으므로 세 번째 예를 추가하겠습니다.
int stupid_method(Base *b) {
if( dynamic_cast<Foo*>(b) ) return 0;
if( dynamic_cast<Bar*>(b) ) return 1;
return 2;
}
Bar b;
stupid_method(&b); // Would expect 0
stupid_method(&b.foo); //Would expect 1
그러나 마지막 두 통화는 동일합니다.
오래된 예제 (빈 클래스에는 가상 메서드가 포함되어 있지 않을 수 있으므로 질문에 대답하지 않을 것입니다.)
위의 코드에서 (가상 소멸자가 추가 된) 다음 예제를 고려하십시오.
void delBase(Base *b) {
delete b;
}
Bar *b = new Bar;
delBase(b); // One would expect this to be absolutely fine.
delBase(&b->foo); // Whoaa, we shouldn't delete a member variable.
그러나 컴파일러는이 두 경우를 어떻게 구별해야합니까?
그리고 아마도 조금 덜 생각할 수도 있습니다.
struct Base {
virtual void hi() { std::cout << "Hello\n";}
};
struct Foo : Base {
void hi() override { std::cout << "Guten Tag\n";}
};
struct Bar : Base {
Foo foo;
};
Bar b;
b.hi() // Hello
b.foo.hi() // Guten Tag
Base *a = &b;
Base *z = &b.foo;
a->hi() // Hello
z->hi() // Guten Tag
그러나 빈 기본 클래스 최적화가 있다면 마지막 두 개는 동일합니다!