<

Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x

Virtual base classes (Diamond problem)

Published on
13,063 Points
6,663 Views
4 Endorsements
Last Modified:
The following diagram presents a diamond class hierarchy:
DiamondAs depicted, diamond inheritance denotes when two classes (e.g., CDerived1 and CDerived2), separately extending a common base class (e.g., CBase), are sub classed simultaneously by a fourth class (e.g., CTest).

The following is how one would expect the diagram to be implemented:
#include <stdio.h>

class CBase
{
public:
	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CDerived1 : public CBase
{
};

class CDerived2 : public CBase
{
};

class CTest : public CDerived1, public CDerived2
{
};

int main()
{
	CTest test;
	test.test();
	return 0;
}

Open in new window

However, the Microsoft VC+ 2005 .NET compiler gives the following message:
1>------ Build started: Project: vft_test, Configuration: Debug Win32 ------
1>Compiling...
1>main_romb.cpp
1>c:\source\vft_test\vft_test\main_romb.cpp(27) : error C2385: ambiguous access of 'test'
1>        could be the 'test' in base 'CBase'
1>        or could be the 'test' in base 'CBase'
1>c:\source\vft_test\vft_test\main_romb.cpp(27) : error C3861: 'test': identifier not found
1>Build log was saved at "file://c:\Source\vft_test\vft_test\Debug\BuildLog.htm"
1>vft_test - 2 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Open in new window


Since CDerived1 and CDerived2 can have very different implementations of CBase::test(), the compiler is unsure which CTest derives.  This is the so called "Diamond problem" or even "Dreaded Diamond".

How to deal with this "dread"?

The following code will give you a clue:
#include <stdio.h>

class CBase
{

public:
	CBase()
	{
		printf("CBase::CBase()");
	}

	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CDerived1 : public CBase
{

public:
	CDerived1()
	{
		printf("CDerived1::CDerived1()");
	}
};

class CDerived2 : public CBase
{

public:
	CDerived2()
	{
		printf("CDerived1::CDerived1()");
	}
};

class CTest : public CDerived1, public CDerived2
{

public:
	CTest()
	{
		printf("CTest::CTest()");
	}
};

int main()
{
	CTest test;
	return 0;
}

Open in new window


This program is compiled successfully and works:
Object creation process
Of course, you know how CTest object is created.  It derives, firstly, from CDerived1 object, that inherits from CBase, and, secondly, CTest derives from CDerived2 that also inherits from CBase.  So the compiler (probably any compiler) creates CBase object, then CDerived1, and now it needs to create CBase again, ..., etc., we got 2 CBase objects!

The diagram did not show that the CBase class appears twice in the class hierarchy.

C++ provides a technique for such case.  The declaration of the CBase class as virtual solves the situation:
#include <stdio.h>

class CBase
{
public:
	CBase()
	{
		printf("CBase::CBase()\r\n");
	}
	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CDerived1 : virtual public CBase
{
public:
	CDerived1()
	{
		printf("CDerived1::CDerived1()\r\n");
	}
};

class CDerived2 : virtual public CBase
{
public:
	CDerived2()
	{
		printf("CDerived2::CDerived1()\r\n");
	}
};

class CTest : public CDerived1, public CDerived2
{
public:
	CTest()
	{
		printf("CTest::CTest()\r\n");
	}
};

int main()
{
	CTest test;

	test.test();

	return 0;
}

Open in new window

The application output is shown on the following screen shot:
Virtual classesIn this example CBase is inherited virtually.  CDerived1 and CDerived2 classes share the same implementation of CBase.  The compiler creates only one CBase object.

Of course, it is possible to solve this "Diamond problem" in another way.   For example, you can explicitly specify which of the super class implementations of test() is desired:
#include <stdio.h>

class CBase
{
public:
	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CDerived1 : public CBase
{
};

class CDerived2 : public CBase
{
};

class CTest : public CDerived1, public CDerived2
{
};

int main()
{
	CTest test;
	test.CDerived1::test();
	return 0;
}

Open in new window


I think that the best solution is to try to avoid such diamond problems.  That will keep the design simple.

The following source code shows how we can use aggregation instead of the diamond inheritance with our same classes:
#include <stdio.h>

class CBase
{
public:
	virtual void test()
	{
		printf("CBase::test()\r\n");
	}
};

class CDerived1 : public CBase
{
};

class CDerived2 : public CBase
{
};

class CTest
{
	CDerived1 m_One;
	CDerived2 m_Two;
public:
	void test()
	{
		m_One.test();
		m_Two.test();
		//or just
		//m_One.test();
	}
};

int main()
{
	CTest test;
	test.test();
	return 0;
}

Open in new window


The caveat to this method, though, is CTest object will be bigger in memory as CTest contains CDerived1 and CDerived2, which will have their own copy of CBase as discussed above.

Hopefully you have found this useful for understanding and overcoming the "Diamond problem", but for more information on the subject please see the following:
http://en.wikipedia.org/wiki/Diamond_problem
http://en.wikipedia.org/wiki/Virtual_inheritance
4
Comment
Author:pgnatyuk
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
3 Comments
 

Expert Comment

by:dimadon
Hello , and thank you,

now I understand. It solves a problem in my current project.

0
 
LVL 60

Expert Comment

by:Kevin Cross
Good job, pgnatyuk!
You have my vote above.
0
 

Expert Comment

by:data4use
Thanks a lot man, it helped me
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Join & Write a Comment

In this video you will find out how to export Office 365 mailboxes using the built in eDiscovery tool. Bear in mind that although this method might be useful in some cases, using PST files as Office 365 backup is troublesome in a long run (more on t…
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month