Topic: virtual inheritance
Author: Alex Vinokur <alexander.vinokur@telrad.co.il>
Date: 1999/04/20 Raw View
In article <371070FE.CBBE4987@club-internet.fr>,
Patrick =?iso-8859-1?Q?M=E9rissert=2DCoffini=E8res?= <pamc@club-internet.fr>
wrote:
> Yes, this a well-kown point about virtual multi-inheritance: you have to
> specify the base ctor argument(s) in the most derived class (indeed in any
> derived class however remoted):
>
[snip]
Hi,
Here is an example with virtual multi-inheritance.
Alex
//#########################################################
//------------------- C++ code : BEGIN -------------------
/*
Here is a classes hierarchy.
AAA
|
|
BBB
|
__________|__________
| |
| |
CCC1 CCC2
| |
|___________________|
|
|
DDD
|
|
EEE
Here is a C++ program which implements the above hierarchy.
*/
//##############################################
#include <iostream>
//================ AAA ==========================
class AAA
{
private:
int valueAAA_;
public:
AAA (int valueAAA_i)
{
cout << __PRETTY_FUNCTION__ << endl;
// __PRETTY_FUNCTION__ is predefined variable
// in GNU gcc/g++/egcs
valueAAA_ = valueAAA_i;
}
void show_valueAAA()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueAAA = "
<< valueAAA_
<< endl;
}
};
//================ BBB ==========================
class BBB : public AAA
{
private:
int valueBBB_;
public:
BBB (int valueAAA_i,
int valueBBB_i)
: AAA (valueAAA_i)
{
cout << __PRETTY_FUNCTION__ << endl;
valueBBB_ = valueBBB_i;
}
void show_valueBBB()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueBBB = "
<< valueBBB_
<< endl;
}
};
//================ CCC1 ==========================
class CCC1 : virtual public BBB
{
private:
int valueCCC1_;
public:
CCC1 (int valueAAA_i,
int valueBBB_i,
int valueCCC1_i
)
: BBB (valueAAA_i,
valueBBB_i
)
{
cout << __PRETTY_FUNCTION__ << endl;
valueCCC1_ = valueCCC1_i;
}
void show_valueCCC1()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueCCC1 = "
<< valueCCC1_
<< endl;
}
};
//================ CCC2 ==========================
class CCC2 : virtual public BBB
{
private:
int valueCCC2_;
public:
CCC2 (int valueAAA_i,
int valueBBB_i,
int valueCCC2_i)
: BBB (valueAAA_i,
valueBBB_i
)
{
cout << __PRETTY_FUNCTION__ << endl;
valueCCC2_ = valueCCC2_i;
}
void show_valueCCC2()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueCCC2 = "
<< valueCCC2_
<< endl;
}
};
//================ DDD ==========================
class DDD : public CCC1, public CCC2
{
private:
int valueDDD_;
public:
DDD (int valueAAA_i,
int valueBBB_i,
int valueCCC1_i,
int valueCCC2_i,
int valueDDD_i
)
//===========================================
// We must call BBB-constructor here
: BBB (valueAAA_i,
valueBBB_i
),
//---------------------------
CCC1 (valueAAA_i + 1, // It will be ignored
valueBBB_i + 1, // It will be ignored
valueCCC1_i
),
// So, it is worth using here
// the following CCC1-constructor :
// CCC1 (int valueCCC1_i).
// This constructor must be added in class CCC1
//---------------------------
CCC2 (valueAAA_i + 2, // It will be ignored
valueBBB_i + 2, // It will be ignored
valueCCC2_i
)
// So, it is worth using here
// the following CCC2-constructor :
// CCC2 (int valueCCC2_i).
// This constructor must be added in class CCC2
//===========================================
{
cout << __PRETTY_FUNCTION__ << endl;
valueDDD_ = valueDDD_i;
}
void show_valueDDD()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueDDD = "
<< valueDDD_
<< endl;
}
};
//================ EEE ==========================
class EEE : public DDD
{
private:
int valueEEE_;
public:
EEE (int valueAAA_i,
int valueBBB_i,
int valueCCC1_i,
int valueCCC2_i,
int valueDDD_i,
int valueEEE_i
)
//===========================================
// We must call BBB-constructor here
: BBB (valueAAA_i,
valueBBB_i
),
//---------------------------
DDD (valueAAA_i + 1, // It will be ignored
valueBBB_i + 1, // It will be ignored
valueCCC1_i,
valueCCC2_i,
valueDDD_i
)
// So, it is worth using here
// the following DDD-constructor :
// DDD (int valueCCC1_i,
// int valueCCC2_i,
// int valueDDD_i
// )
// This constructor must be added in class DDD
//===========================================
{
cout << __PRETTY_FUNCTION__ << endl;
valueEEE_ = valueEEE_i;
}
void show_valueEEE()
{
cout << __PRETTY_FUNCTION__
<< " \t: valueEEE = "
<< valueEEE_
<< endl;
}
};
//########################################
int main()
{
//=========================
cout << endl << "\t=== AAA object ===" << endl;
AAA aaa (10);
aaa.show_valueAAA ();
//=========================
cout << endl << "\t=== BBB object ===" << endl;
BBB bbb (100, 200);
bbb.show_valueAAA ();
bbb.show_valueBBB ();
//=========================
cout << endl << "\t=== CCC1 object ===" << endl;
CCC1 ccc1 (1000, 2000, 3000);
ccc1.show_valueAAA ();
ccc1.show_valueBBB ();
ccc1.show_valueCCC1 ();
//=========================
cout << endl << "\t=== CCC2 object ===" << endl;
CCC2 ccc2 (10000, 20000, 30000);
ccc2.show_valueAAA ();
ccc2.show_valueBBB ();
ccc2.show_valueCCC2 ();
//=========================
cout << endl << "\t=== DDD object ===" << endl;
DDD ddd (100000, 200000, 300000, 400000, 500000);
ddd.show_valueAAA ();
ddd.show_valueBBB ();
ddd.show_valueCCC1 ();
ddd.show_valueCCC2 ();
ddd.show_valueDDD ();
//=========================
cout << endl << "\t=== EEE object ===" << endl;
EEE eee (1000000, 2000000, 3000000, 4000000, 5000000, 6000000);
eee.show_valueAAA ();
eee.show_valueBBB ();
eee.show_valueCCC1 ();
eee.show_valueCCC2 ();
eee.show_valueDDD ();
eee.show_valueEEE ();
return 0;
}
//------------------- C++ code : END ----------------------
//#########################################################
//------------------- Running Results : BEGIN -------------
=== AAA object ===
AAA::AAA(int)
void AAA::show_valueAAA() : valueAAA = 10
=== BBB object ===
AAA::AAA(int)
BBB::BBB(int, int)
void AAA::show_valueAAA() : valueAAA = 100
void BBB::show_valueBBB() : valueBBB = 200
=== CCC1 object ===
AAA::AAA(int)
BBB::BBB(int, int)
CCC1::CCC1(int, int, int)
void AAA::show_valueAAA() : valueAAA = 1000
void BBB::show_valueBBB() : valueBBB = 2000
void CCC1::show_valueCCC1() : valueCCC1 = 3000
=== CCC2 object ===
AAA::AAA(int)
BBB::BBB(int, int)
CCC2::CCC2(int, int, int)
void AAA::show_valueAAA() : valueAAA = 10000
void BBB::show_valueBBB() : valueBBB = 20000
void CCC2::show_valueCCC2() : valueCCC2 = 30000
=== DDD object ===
AAA::AAA(int)
BBB::BBB(int, int)
CCC1::CCC1(int, int, int)
CCC2::CCC2(int, int, int)
DDD::DDD(int, int, int, int, int)
void AAA::show_valueAAA() : valueAAA = 100000
void BBB::show_valueBBB() : valueBBB = 200000
void CCC1::show_valueCCC1() : valueCCC1 = 300000
void CCC2::show_valueCCC2() : valueCCC2 = 400000
void DDD::show_valueDDD() : valueDDD = 500000
=== EEE object ===
AAA::AAA(int)
BBB::BBB(int, int)
CCC1::CCC1(int, int, int)
CCC2::CCC2(int, int, int)
DDD::DDD(int, int, int, int, int)
EEE::EEE(int, int, int, int, int, int)
void AAA::show_valueAAA() : valueAAA = 1000000
void BBB::show_valueBBB() : valueBBB = 2000000
void CCC1::show_valueCCC1() : valueCCC1 = 3000000
void CCC2::show_valueCCC2() : valueCCC2 = 4000000
void DDD::show_valueDDD() : valueDDD = 5000000
void EEE::show_valueEEE() : valueEEE = 6000000
//------------------- Running Results : END ---------------
//#########################################################
//------------------- Compiler & System ------------------
g++ -v : gcc version egcs-2.91.57 19980901
(egcs-1.1 release)
uname -a : SunOS <nodename> 5.6 Generic_105181-09
sun4m sparc SUNW,SPARCstation-5
//---------------------------------------------------------
//#########################################################
-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: "Iavor S. Diatchki" <idiatchk@cs.uct.ac.za.remove_me>
Date: 1999/04/11 Raw View
hello,
i have just stumbled onto something new for me and i could not find an
explanation in the c++ standard (2 december 96) so i would appreciate an
explanation from someone if they know... consider the following setup:
struct a {
a() {}
a(int) {}
};
struct b : public virtual a {
b(int) : a(3) {}
};
struct c : public virtual a {
c(int) : a(3) {}
};
struct d : public c, public d {
d(int) : c(3), d(3) {}
};
This should look like:
a
b c
d
i was expecting that when creating an object of class 'd', for example new
d(3), the constructor taking an integer in the class 'a' should be called.
however i found that the default constructor is always called, for class
'a', and the declaration in the initialiser list for classes 'c' and 'd'
is being ignored. i realise that the above design is not great, and
might easily lead to ambiguous situations (eg what if 'b' and 'c' tried to
create 'a' in different ways?), but nevertheless am interested to know if
the behaviour of my compiler is according to the c++ standard or is
specific to the implementation. i tried the above example on gcc 2.7.1.
yavor
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Martin von Loewis <loewis@informatik.hu-berlin.de>
Date: 1999/04/11 Raw View
"Iavor S. Diatchki" <idiatchk@cs.uct.ac.za.remove_me> writes:
> i was expecting that when creating an object of class 'd', for example new
> d(3), the constructor taking an integer in the class 'a' should be called.
> however i found that the default constructor is always called, for class
> 'a', and the declaration in the initialiser list for classes 'c' and 'd'
> is being ignored. i realise that the above design is not great, and
> might easily lead to ambiguous situations (eg what if 'b' and 'c' tried to
> create 'a' in different ways?), but nevertheless am interested to know if
> the behaviour of my compiler is according to the c++ standard or is
> specific to the implementation.
The behaviour you observe is compliant, and you also gave the reason
for that behaviour: the base classes *can't* initialize the virtual
bases, because they may attempt to do that in different ways.
So in C++, virtual bases are always initialized by the most-derived
class. To get what you want, write
d(int) : a(3), c(3), d(3) {}
Hope this helps,
Martin
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]
Author: Patrick =?iso-8859-1?Q?M=E9rissert=2DCoffini=E8res?= <pamc@club-internet.fr>
Date: 1999/04/11 Raw View
Yes, this a well-kown point about virtual multi-inheritance: you have to
specify the base ctor argument(s) in the most derived class (indeed in any
derived class however remoted):
struct d: public b, public c{
d(int):c(3), b(3), a(3)
{
}
// ...
};
which is indeed strange (it is illegal in any other situation) and not very
consistent with encapsulation. This of course handles the problem of
diverging calls to a ctor from b and c.
I certainly don't want to start a flame war but in my opinion, this doesn't
mean that multi-inheritance should be avoided: it only means all
syntactically possible forms are not equally good to use, and safe one
include:
- non virtual multi inheritance for independant subclassing (assuming each
individual inheritance is sound from the design point of view)
- multi-inheritance of classes with a common virtual superclass that only
needs a default ctor, so that the final client doesn't have to know about it.
The best example of this is a totally abstract class, in the style of java
interfaces, but there can be other cases.
I think that potential virtual, multi-inheritance root classes that need
arguments passed to the ctors should be avoided, you have noted yourself some
of the problems that could arise.
Your compiler is ok, if "a" had not been provided with a default ctor, you
should have got an error message that might have been more explicit, but I
have not tried that recently.
"Iavor S. Diatchki" wrote:
> hello,
>
> i have just stumbled onto something new for me and i could not find an
> explanation in the c++ standard (2 december 96) so i would appreciate an
> explanation from someone if they know... consider the following setup:
>
> struct a {
> a() {}
> a(int) {}
> };
>
> struct b : public virtual a {
> b(int) : a(3) {}
> };
>
> struct c : public virtual a {
> c(int) : a(3) {}
> };
>
> struct d : public c, public d {
> d(int) : c(3), d(3) {}
> };
>
> This should look like:
> a
> b c
> d
>
> i was expecting that when creating an object of class 'd', for example new
> d(3), the constructor taking an integer in the class 'a' should be called.
> however i found that the default constructor is always called, for class
> 'a', and the declaration in the initialiser list for classes 'c' and 'd'
> is being ignored. i realise that the above design is not great, and
> might easily lead to ambiguous situations (eg what if 'b' and 'c' tried to
> create 'a' in different ways?), but nevertheless am interested to know if
> the behaviour of my compiler is according to the c++ standard or is
> specific to the implementation. i tried the above example on gcc 2.7.1.
>
> yavor
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html ]