Virtual base classes (Diamond problem)

AID: 1246
  • Status: Published

3790 points

  • Bypgnatyuk
  • TypeTutorial
  • Posted on2009-07-26 at 00:01:06
The following diagram presents a diamond class hierarchy:
image002.gif
  • 4 KB
  • Diamond
Diamond

As 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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:

Select allOpen 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 ==========
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Select allOpen 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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:

Select allOpen in new window



This program is compiled successfully and works:
image003.png
  • 6 KB
  • Object creation process
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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:

Select allOpen in new window


The application output is shown on the following screen shot:
image006.jpg
  • 9 KB
  • Virtual classes
Virtual classes

In 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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:

Select allOpen 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;
}
                                    
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:

Select allOpen 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
    Asked On
    2009-07-26 at 00:01:06ID1246
    Tags

    Microsoft Visual C++

    ,

    Derived Class

    ,

    Virtual Inheritance

    ,

    Diamond Problem

    Topic

    Microsoft Visual C++.Net

    Views
    3030

    Comments

    Expert Comment

    by: dimadon on 2009-07-28 at 00:23:45ID: 2355

    Hello , and thank you,

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

    Expert Comment

    by: mwvisa1 on 2009-07-28 at 06:00:27ID: 2358

    Good job, pgnatyuk!
    You have my vote above.

    Expert Comment

    by: data4use on 2010-07-07 at 06:11:43ID: 16709

    Thanks a lot man, it helped me

    Add your Comment

    Please Sign up or Log in to comment on this article.

    Join Experts Exchange Today

    Gain Access to all our Tech Resources

    Get personalized answers

    Ask unlimited questions

    Access Proven Solutions

    Search 3.2 million solutions

    Read In-Depth How-To Guides

    1000+ articles, demos, & tips

    Watch Step by Step Tutorials

    Learn direct from top tech pros

    And Much More!

    Your complete tech resource

    See Plans and Pricing

    30-day free trial. Register in 60 seconds.

    Loading Advertisement...

    Top Visual C++.NET Experts

    1. sarabande

      29,721

      0 points yesterday

      Profile
      Rank: Sage
    2. jkr

      26,180

      0 points yesterday

      Profile
      Rank: Savant
    3. CodeCruiser

      20,000

      0 points yesterday

      Profile
      Rank: Genius
    4. Zoppo

      19,136

      0 points yesterday

      Profile
      Rank: Genius
    5. AndyAinscow

      18,920

      0 points yesterday

      Profile
      Rank: Genius
    6. kaufmed

      17,352

      0 points yesterday

      Profile
      Rank: Genius
    7. ambience

      8,896

      0 points yesterday

      Profile
      Rank: Sage
    8. JamesBurger

      5,820

      0 points yesterday

      Profile
      Rank: Sage
    9. chris_bottomley

      5,600

      0 points yesterday

      Profile
      Rank: Genius
    10. wdosanjos

      4,888

      0 points yesterday

      Profile
      Rank: Genius
    11. vadimrapp1

      4,500

      0 points yesterday

      Profile
      Rank: Genius
    12. TheLearnedOne

      4,200

      0 points yesterday

      Profile
      Rank: Savant
    13. ve3ofa

      4,200

      0 points yesterday

      Profile
      Rank: Genius
    14. billprew

      4,000

      0 points yesterday

      Profile
      Rank: Genius
    15. plusone3055

      4,000

      0 points yesterday

      Profile
      Rank: Wizard
    16. Idle_Mind

      3,600

      0 points yesterday

      Profile
      Rank: Savant
    17. sedgwick

      3,500

      0 points yesterday

      Profile
      Rank: Genius
    18. tampnic

      3,332

      0 points yesterday

      Profile
      Rank: Master
    19. DarrenD

      2,800

      0 points yesterday

      Profile
      Rank: Sage
    20. huslayer

      2,800

      0 points yesterday

      Profile
      Rank: Sage
    21. tommyBoy

      2,800

      0 points yesterday

      Profile
      Rank: Genius
    22. DanRollins

      2,640

      10 points yesterday

      Profile
      Rank: Genius
    23. Pra4444

      2,332

      0 points yesterday

      Profile
      Rank: Guru
    24. arnold

      2,100

      0 points yesterday

      Profile
      Rank: Genius
    25. RolandDeschain

      2,100

      0 points yesterday

      Profile
      Rank: Sage

    Hall Of Fame