빈 기본 클래스가 멤버 변수 인 경우 빈 기본 최적화가 금지 된 이유는 무엇입니까?


14

빈 기본 최적화 가 훌륭합니다. 그러나 다음과 같은 제한 사항이 있습니다.

비어있는 기본 클래스 중 하나가 첫 번째 비 정적 데이터 멤버의 유형이거나 해당 유형 인 경우 비어있는 기본 최적화는 금지됩니다. 동일한 유형의 두 기본 서브 오브젝트는 오브젝트 표현 내에서 다른 주소를 가져야하기 때문입니다. 가장 많이 파생 된 유형의

이 제한을 설명하려면 다음 코드를 고려하십시오. 는 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인지 또는 그 반대인지는 알 수 없습니다.

실제로 다른 상황에서도 필요하므로 인스턴스가 포함 &fooBar인스턴스 의 주소와 동일한 것으로 예상 됩니다 (1) . 결국, 나는 virtual상속으로 공상을하지 않고 기본 클래스는 비어 있으며, 컴파일 Base2은이 특별한 경우에 아무것도 깨지지 않는다는 것을 보여줍니다.

그러나 분명히이 추론은 어쨌든 부정확하며,이 제한이 필요한 다른 상황이 있습니다.

대답은 C ++ 11 이상이어야합니다 (현재 C ++ 17을 사용하고 있습니다).

(1) 참고 : EBO는 C ++ 11에서 업그레이드되었으며 특히 StandardLayoutTypes의 경우 필수가되었습니다 ( Bar위의 a는 아닙니다 StandardLayoutType).


4
인용 한 이론적 근거 ( " 동일한 유형의 두 기본 하위 객체가 다른 주소를 가져야하기 때문에 ")가 어떻게 부족합니까? 동일한 유형의 서로 다른 개체가 고유 한 주소를 갖도록 요구되며이 요구 사항에 따라 해당 규칙을 위반하지 않습니다. 빈 기본 최적화 여기에 적용한다면, 우리는 가질 수 Base *a = new Bar(); Base *b = a->foo;a==b하지만, a그리고 b명확하게 (아마도 다른 가상 메서드 재정의와) 다른 개체입니다.
Toby Speight

1
언어 변호사 답변은 사양의 관련 부분을 인용합니다. 그리고 당신은 이미 그것에 대해 알고있는 것 같습니다.
중복 제거기

3
나는 당신이 여기에서 어떤 종류의 대답을 찾고 있는지 잘 모르겠습니다. C ++ 객체 모델이 바로 그것입니다. 객체 모델이 필요하기 때문에 제한이 있습니다. 그 이상으로 무엇을 찾고 있습니까?
Nicol Bolas

@TobySpeight 동일한 유형의 다른 개체는 고유 한 주소를 가져야합니다 . 올바르게 정의 된 동작을 가진 프로그램에서이 규칙을 쉽게 위반할 수 있습니다.
언어 변호사

@TobySpeight 아니요, 평생에 대해 말하는 것을 잊어 버린 것은 아닙니다. "같은 수명 을 가진 같은 유형의 다른 객체 " . 동일한 주소에 동일한 유형의 여러 객체가 모두 존재할 수 있습니다. 이를 가능하게하는 문구에는 2 가지 이상의 버그가 있습니다.
언어 변호사

답변:


4

좋아, 그것은 항상 틀린 것처럼 보인다. 모든 예제에는 기본 객체에 대한 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

그러나 빈 기본 클래스 최적화가 있다면 마지막 두 개는 동일합니다!


1
그래도 두 번째 호출에는 정의되지 않은 동작이 있다고 주장 할 수 있습니다. 따라서 컴파일러는 아무것도 구별 할 필요가 없습니다.
StoryTeller-Unslander Monica

1
가상 멤버가있는 클래스는 비어 있지 않으므로 여기와 관련이 없습니다!
중복 제거기

1
@Deduplicator 표준 견적이 있습니까? Cppref는 빈 클래스가 "정적이 아닌 데이터 멤버가없는 클래스 또는 구조체"라고 말합니다.
n314159

1
cppreference의 @ n314159 std::is_empty가 훨씬 더 정교합니다. eel.is 의 현재 초안 과 동일 합니다.
중복 제거기

2
dynamic_cast다형성이 아닌 경우 (여기서는 관련이없는 예외는 제외) 불가능합니다.
TC
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.