Resolution to C++ "dreaded diamond"

(quoted from http://www.parashift.com)

The "dreaded diamond" refers to a class structure in which a particular class appears more than once in a class's inheritance hierarchy. For example,

class Base {
public:
...
protected:
int data_;
};

class Der1 : public Base { ... };

class Der2 : public Base { ... };

class Join : public Der1, public Der2 {
public:
void method()
{
data_ = 1;
bad: this is ambiguous; see below
}
};

int main()
{
Join* j = new Join();
Base* b = j;
bad: this is ambiguous; see below
}

Forgive the ASCII-art, but the inheritance hierarchy looks something like this:

Base
/ /
/ Der1 Der2
\ /
\ /
\ /
Join

Before we explain why the dreaded diamond is dreaded, it is important to note that C++ provides techniques to deal with each of the "dreads." In other words, this structure is often called the dreaded diamond, but it really isn't dreaded; it's more just something to be aware of.

The key is to realize that Base is inherited twice, which means any data members declared in Base, such as data_ above, will appear twice within a Join object. This can create ambiguities: which data_ did you want to change? For the same reason the conversion from Join* to Base*, or from Join& to Base&, is ambiguous: which Base class subobject did you want?

C++ lets you resolve the ambiguities. For example, instead of saying data_ = 1 you could say Der2::data_ = 1, or you could convert from Join* to a Der1* and then to a Base*. However please, Please, PLEASE think before you do that. That is almost always not the best solution. The best solution is typically to tell the C++ compiler that only one Base subobject should appear within a Join object, and that is described


Resolution
----------

Just below the top of the diamond, not at the join-class.

To avoid the duplicated base class subobject that occurs with the "dreaded diamond", you should use the virtual keyword in the inheritance part of the classes that derive directly from the top of the diamond:

class Base {
public:
...
protected:
int data_;
};

class Der1 : public virtual Base {
public: ^^^^^^^
—this is the key
...
};

class Der2 : public virtual Base {
public: ^^^^^^^
—this is the key
...
};

class Join : public Der1, public Der2 {
public:
void method()
{
data_ = 1;
good: this is now unambiguous
}
};

int main()
{
Join* j = new Join();
Base* b = j;
good: this is now unambiguous
}

Because of the virtual keyword in the base-class portion of Der1 and Der2, an instance of Join will have have only a single Base subobject. This eliminates the ambiguities. This is usually better than using full qualification as described in the previous FAQ.

For emphasis, the virtual keyword goes in the hierarchy above Der1 and Der2. It doesn't help to put the virtual keyword in the Join class itself. In other words, you have to know that a join class will exist when you are creating class Der1 and Der2.

Base
/ / virtual
virtual
/ Der1 Der2
\ /
\ /
\ /
Join

Comments

Popular posts from this blog

Outlook : "operation failed, object could not be found. " when trying to close a PST.

How to transfer app and data from old iPhone to new iPhone for model IOS17 and IOS18 above.

Troubleshooting Outlook Express (IMAP, POP3, HTTP, NEWS) with the log file