Topic: Q. Non-virtual vs virtual copy constructors.
Author: plessel@oz.rtpnc.epa.gov (Todd Plessel)
Date: Tue, 14 Dec 1993 21:50:35 GMT Raw View
On page 162 of _Effective C++_ by Scott Meyers, he points out an
asymmetry in the treatment of non-virtual copy constructors vs.
virtual copy constructors. I'm wondering what the rationale is for
this asymmetric treatment. It seems that the virtual case should
behave like the non-virtual case. Also note that the *default*
constructors behave as expected (described on p 293 of The ARM).
For those few of you that haven't yet read Meyers, here is the quote:
"Passing constructor arguments to virtual base classes. Under
nonvirtual inheritance, arguments for a base class constructor are
specified in the member initialization lists of the class immedi-
ately derived from the base class. Because single inheritance uses
only nonvirtual bases, arguments are passed up the inheritance
hierarchy in a very natural fashion: the classes at level n of the hi-
erarchy pass arguments to the classes at level n-1. For construc-
tors of a virtual base class, however, arguments are specified in
the member initialization lists of the *most derived* from the
base. As a result, the class initializing a virtual base may be arbi-
trarily far from it in the inheritance graph, and furthermore, the
class performing the initialization can change as new classes are
added to the hierarchy."
To illustrate, compile and run the below program, first with '#if 0'
then with '#if 1' and note that in the second case (line 4 of the output)
the Base's *default* constructor is called instead of the Base's *copy*
constructor. (At least on my cfront-based compiler. If you get different
results let me know.)
On pages 201-203 of _C++ Programming Style_ by Tom Cargill, he offers a
solution for proper implementation of operator= in the presence of virtual
classes. But the copy constructor case seems a little different. Perhaps it
is not. Can someone straighten me out on this? What is the rationale for the
different treatment of copy constructors in the non-virtual and virtual cases?
Thanks,
Todd
-----
Sample code to illustrate. (This minimal example is SI.)
-----
#include <iostream.h>
#if 1
#define VIRTUAL virtual
#else
#define VIRTUAL
#endif
//*************************************
class Base
{
public:
VIRTUAL ~Base();
Base();
Base( const Base& b );
};
Base::~Base()
{
cout << "~Base()" << endl;
}
Base::Base()
{
cout << "Base()" << endl;
}
Base::Base( const Base& b )
{
cout << "Base( const Base& b )" << endl;
}
//*************************************
class Derived1 : VIRTUAL public Base
{
public:
~Derived1();
Derived1();
Derived1( const Derived1& b );
};
Derived1::~Derived1()
{
cout << "~Derived1()" << endl;
}
Derived1::Derived1()
{
cout << "Derived1()" << endl;
}
Derived1::Derived1( const Derived1& b ) : Base( b ) // <-- ?? seems to call Base() instead!
{
cout << "Derived1( const Derived1& b )" << endl;
}
//*************************************
class Derived2 : VIRTUAL public Derived1
{
public:
~Derived2();
Derived2();
Derived2( const Derived2& b );
};
Derived2::~Derived2()
{
cout << "~Derived2()" << endl;
}
Derived2::Derived2()
{
cout << "Derived2()" << endl;
}
Derived2::Derived2( const Derived2& b ) : Derived1( b )
{
cout << "Derived2( const Derived2& b )" << endl;
}
//*************************************
int main()
{
Derived2 x;
Derived2 y( x );
return 0;
}
Author: rmartin@rcmcon.com (Robert Martin)
Date: Wed, 15 Dec 1993 15:05:57 GMT Raw View
plessel@oz.rtpnc.epa.gov (Todd Plessel) writes:
>Can someone straighten me out on this? What is the rationale for the
>different treatment of copy constructors in the non-virtual and virtual cases?
Consider:
class V { public: V(const V& v) :itsInt(v.itsInt) {}};
class B1 : public virtual V { public: B1(const B1& b) : V(b) {}};
class B2 : public virtual V { public: B2(const B2& b) : V(b) {}};
class D : public B1, public B2 {public D(const D& d) : B1(d), B2(d) {}};
Now, when you invoke the copy constructor for D, how many times will
the copy constructor for V get called? How many times do you want it
to be called?
Without the asymetry between virtual and non-virtual bases, V would be
constructed twice. This is a bad thing. Thus the above declaration
for D is illegal. It should look like this:
class D : public B1, public B2
{
public:
D(const D& d)
: V(d), B1(d), B2(d)
{}
};
When D is constructed, V will be initialized once by the D
constructor. B1 and B2 will also be initialized, however the
initializers for V in their constructors will be ignored.
--
Robert Martin | Design Consulting | Training courses offered:
Object Mentor Assoc.| rmartin@rcmcon.com | Object Oriented Analysis
2080 Cranbrook Rd. | Tel: (708) 918-1004 | Object Oriented Design
Green Oaks IL 60048 | Fax: (708) 918-1023 | C++