Everything we've seen so far applies to both single and multiple inheritance, but when multiple inheritance enters the picture, things get more complex (see Item E43). There is no point in dwelling on details, but with multiple inheritance, offset calculations to find vptrs within objects become more complicated; there are multiple vptrs within a single object (one per base class); and special vtbls must be generated for base classes in addition to the stand alone vtbls we have discussed. As a result, both the per-class and the per-object space overhead for virtual functions increases, and the runtime invocation cost grows slightly, too.
지금까지 살펴본 모든 내용은 단일 상속과 다중 상속에 모두 적용되지만 다중 상속이 포함되면 상황이 더 복잡해집니다(항목 E43 참조). 세부 사항에 대해 설명할 필요는 없지만 다중 상속을 사용하면 객체 내에서 vptr을 찾기 위한 오프셋 계산이 더 복잡해집니다. 단일 객체 내에 여러 vptr이 있고(기본 클래스당 하나씩) 독립형 vtbl 외에도 기본 클래스에 대한 특수 vtbl을 생성해야 합니다. 결과적으로 가상 함수에 대한 클래스별 및 객체별 공간 오버헤드가 모두 증가하고 런타임 호출 비용도 약간 증가합니다.
Multiple inheritance often leads to the need for virtual base classes. Without virtual base classes, if a derived class has more than one inheritance path to a base class, the data members of that base class are replicated within each derived class object, one copy for each path between the derived class and the base class. Such replication is almost never what programmers want, and making base classes virtual eliminates the replication.
다중 상속은 종종 가상 기본 클래스가 필요하게 만듭니다. 가상 기본 클래스가 없으면 파생 클래스에 기본 클래스로 가는 상속 경로가 두 개 이상 있는 경우 해당 기본 클래스의 데이터 멤버가 파생 클래스 객체 내에서 복제되고 파생 클래스와 기본 클래스 간의 경로마다 복사본이 하나씩 있습니다. 이러한 복제는 프로그래머가 거의 원하지 않는 일이며 기본 클래스를 가상으로 만들면 복제가 제거됩니다.
Virtual base classes may incur a cost of their own, however, because implementations of virtual base classes often use pointers to virtual base class parts as the means for avoiding the replication, and one or more of those pointers may be stored inside your objects.
For example, consider this, which I generally call "the dreaded multiple inheritance diamond:"
가상 기본 클래스는 자체 비용이 발생할 수 있지만, 가상 기본 클래스 구현은 종종 복제를 피하기 위한 수단으로 가상 기본 클래스 부분에 대한 포인터를 사용하고, 이러한 포인터 중 하나 이상이 개체 내부에 저장될 수 있기 때문입니다. 예를 들어, 일반적으로 "두려운 다중 상속 다이아몬드"라고 부르는 이것을 고려해 보세요.
Here A is a virtual base class because B and C virtually inherit from it. With some compilers (especially older compilers), the layout for an object of type D is likely to look like this:
여기서 A는 B와 C가 가상 기본 클래스에서 상속하기 때문에 가상 기본 클래스입니다. 일부 컴파일러(특히 오래된 컴파일러)의 경우 D 유형의 개체에 대한 레이아웃은 다음과 같습니다.
It seems a little strange to place the base class data members at the end of the object, but that's often how it's done. Of course, implementations are free to organize memory any way they like, so you should never rely on this picture for anything more than a conceptual overview of how virtual base classes may lead to the addition of hidden pointers to your objects.
Some implementations add fewer pointers, and some find ways to add none at all. (Such implementations make the vptr and vtbl serve double duty).
기본 클래스 데이터 멤버를 개체의 끝에 배치하는 것은 약간 이상하게 보이지만, 종종 그렇게 합니다. 물론 구현은 원하는 대로 메모리를 구성할 수 있으므로 가상 기본 클래스가 개체에 대한 숨겨진 포인터를 추가하는 방식으로 이어질 수 있는 방법에 대한 개념적 개요 이상을 위해 이 그림에 의존해서는 안 됩니다. 일부 구현은 포인터를 덜 추가하고, 일부는 전혀 추가하지 않는 방법을 찾습니다. (이러한 구현은 vptr과 vtbl을 이중으로 사용합니다.)
If we combine this picture with the earlier one showing how virtual table pointers are added to objects, we realize that if the base class A in the hierarchy on page 119 has any virtual functions, the memory layout for an object of type D could look like this:
이 그림을 119페이지의 계층 구조에서 기본 클래스 A에 가상 함수가 있는 경우 D 유형의 객체에 대한 메모리 레이아웃이 다음과 같이 보일 수 있음을 알 수 있습니다.
Here I've shaded the parts of the object that are added by compilers. The picture may be misleading, because the ratio of shaded to unshaded areas is determined by the amount of data in your classes. For small classes, the relative overhead is large. For classes with more data, the relative overhead is less significant, though it is typically noticeable.
여기서 컴파일러가 추가한 객체의 부분을 음영 처리했습니다. 음영 처리된 영역과 음영 처리되지 않은 영역의 비율은 클래스의 데이터 양에 따라 결정되므로 그림이 오해의 소지가 있을 수 있습니다. 작은 클래스의 경우 상대적 오버헤드가 큽니다. 데이터가 많은 클래스의 경우 상대적 오버헤드는 덜 중요하지만 일반적으로 눈에 띕니다.
An oddity in the above diagram is that there are only three vptrs even though four classes are involved.
Implementations are free to generate four vptrs if they like, but three suffice (it turns out that B and D can share a vptr), and most implementations take advantage of this opportunity to reduce the compiler-generated overhead.
위 다이어그램의 이상한 점은 4개의 클래스가 관련되어 있음에도 불구하고 vptr이 3개뿐이라는 것입니다. 구현은 원하는 경우 4개의 vptr을 자유롭게 생성할 수 있지만 3개로 충분합니다(B와 D는 vptr을 공유할 수 있음이 밝혀졌습니다). 대부분의 구현은 컴파일러에서 생성한 오버헤드를 줄이기 위해 이 기회를 활용합니다.
We've now seen how virtual functions make objects larger and preclude inlining, and we've examined how multiple inheritance and virtual base classes can also increase the size of objects. Let us therefore turn to our final topic, the cost of runtime type identification (RTTI).
이제 가상 함수가 객체를 더 크게 만들고 인라인을 배제하는 방법을 살펴보았고, 다중 상속과 가상 기본 클래스가 객체의 크기를 어떻게 증가시킬 수 있는지도 살펴보았습니다. 따라서 마지막 주제인 런타임 유형 식별(RTTI)의 비용으로 넘어가겠습니다.
RTTI lets us discover information about objects and classes at runtime, so there has to be a place to store the information we're allowed to query. That information is stored in an object of type type_info, and you can access the type_info object for a class by using the typeid operator.
RTTI를 사용하면 런타임에 객체와 클래스에 대한 정보를 검색할 수 있으므로 쿼리할 수 있는 정보를 저장할 곳이 있어야 합니다. 해당 정보는 type_info 유형의 객체에 저장되며 typeid 연산자를 사용하여 클래스의 type_info 객체에 액세스할 수 있습니다.
There only needs to be a single copy of the RTTI information for each class, but there must be a way to get to that information for any object. Actually, that's not quite true.
각 클래스에 대한 RTTI 정보의 사본은 하나만 있으면 되지만 모든 객체에 대한 해당 정보에 접근할 수 있는 방법이 있어야 합니다. 사실, 그것은 사실이 아닙니다.
The language specification states that we're guaranteed accurate information on an object's dynamic type only if that type has at least one virtual function.
언어 사양은 해당 유형에 최소한 하나의 가상 함수가 있는 경우에만 객체의 동적 유형에 대한 정확한 정보를 보장한다고 명시합니다.
This makes RTTI data sound a lot like a virtual function table. We need only one copy of the information per class, and we need a way to get to the appropriate information from any object containing a virtual function. This parallel between RTTI and virtual function tables is no accident: RTTI was designed to be implementable in terms of a class's vtbl.
For example, index 0 of a vtbl array might contain a pointer to the type_info object for the class corresponding to that vtbl. The vtbl for class C1 on page 114 would then look like this:
따라서 RTTI 데이터는 가상 함수 테이블과 매우 유사합니다. 클래스당 정보 사본은 하나만 필요하고 가상 함수가 포함된 모든 객체에서 적절한 정보에 접근할 수 있는 방법이 필요합니다. RTTI와 가상 함수 테이블 간의 이러한 유사점은 우연이 아닙니다. RTTI는 클래스의 vtbl 측면에서 구현 가능하도록 설계되었습니다. 예를 들어, vtbl 배열의 인덱스 0에는 해당 vtbl에 해당하는 클래스의 type_info 객체에 대한 포인터가 포함될 수 있습니다. 그러면 114페이지의 클래스 C1에 대한 vtbl은 다음과 같습니다.
With this implementation, the space cost of RTTI is an additional entry in each class vtbl plus the cost of the storage for the type_info object for each class. Just as the memory for virtual tables is unlikely to be noticeable for most applications, however, you're unlikely to run into problems due to the size of type_info objects.
이 구현을 사용하면 RTTI의 공간 비용은 각 클래스 vtbl의 추가 항목과 각 클래스의 type_info 객체에 대한 저장소 비용입니다. 그러나 대부분의 애플리케이션에서 가상 테이블의 메모리가 눈에 띄지 않을 가능성이 높지만 type_info 객체의 크기로 인해 문제가 발생할 가능성은 낮습니다.
'컴퓨터공학 > C++' 카테고리의 다른 글
[C++] 함수 객체, 콜백 함수 (0) | 2024.11.19 |
---|---|
[C++] #include <algorithm> (0) | 2024.11.17 |
[C++] 함수 포인터 (0) | 2024.11.13 |
[C++] using (0) | 2024.11.09 |
[C++] noexcept (0) | 2024.11.09 |