I face the well known "dreaded" diamond situation :
A
/ \
B1 B2
\ /
C
|
D
The class A
has, say the constructor A::A(int i)
. I also want to forbid a default instantiation of a A
so I declare the default constructor of A
as private
.
The classes B1
and B2
are virtually derived from A
and have some constructors and a protected
default constructor.
[edit]
The constructors of B1
and B2
don't call the default constructor of A
.
[reedit]
The default constructors of B1
and B2
don't call the default constructor of A
either.
[reedit]
[edit]
The class C
is an abstract class and has some constructors that don't call any of the A
, B1
or B2
constructors.
In the class D
, I call the constructor A::A(i)
and some constructor of C
.
So as expected, when D
is created, it first creates a A
to solve the dreaded diamond problem, then it creates B1
, B2
and C
. Therefore there is no call of the default constructor of A
in B1
, B2
and C
because if there was, it would create many instances of A
.
The compiler rejects the code because the default constructor of A
is private
. If I set it to protected
it compiles.
What I don't understand is that when I run the code, the default constructor of A
is never called (as it should be). So why doesn't the compiler allow me to set it as private
?
[edit]
okay I'll write an example... but it hurts ;-)
class A{
public:
A(int i):i_(i){};
virtual ~A(){};
protected:
int i_;
private:
A():i_(0){};/*if private => compilation error, if protected => ok*/
};
class B1: public virtual A{
public:
B1(int i):A(i){};
virtual ~B1(){};
protected:
B1():A(0){};
};
class B2: public virtual A{
public:
B2(int i):A(i){};
virtual ~B2(){};
protected:
B2():A(0){};
};
class C: public B1, public B2{
public:
C(int j):j_(j){};
virtual ~C()=0;
protected:
int j_;
};
C::~C(){};
class D: public C{
public:
D(int i,int j):A(i),C(j){};
~D(){};
};
int main(){
D d(1,2);
}
The compiler says that in constructor of C
, A::A()
is private. I agree with this, but as C
is an abstract class, it can't be instantiated as a complete object (but it can be instantiated as a base class subobject, by instantiating a D
).
[edit]
I added the tag `language-lawer' on someone's recommendation.
C++ doesn't have an access control specifier for member functions that can only be called from a derived class, but a constructor for an abstract class can only be called from a derived class by definition of an abstract class.
The compiler cannot know in advance exactly which classes are instantiated (this is a runtime property), and it cannot know which constructors are potentially called before link-time.
The standard text (emphasis mine):
All sub-objects representing virtual base classes are initialized by
the constructor of the most derived class (1.8 [intro.object]). If the
constructor of the most derived class does not specify a
mem-initializer for a virtual base class V, then V's default
constructor is called to initialize the virtual base class subobject.
If V does not have an accessible default constructor, the
initialization is ill-formed. A mem-initializer naming a virtual base
class shall be ignored during execution of the constructor of any
class that is not the most derived class.
1) It makes no exception for abstract classes and can only be interpreted as saying that all constructors should do a (sometimes fake) attempt at calling virtual base constructors.
2) It says that at runtime such attempts are ignored.
Some committee members have stated a different opinion in DR 257:
- Abstract base constructors and virtual base initialization
Section: 12.6.2 [class.base.init] Status: CD2 Submitter: Mike
Miller Date: 1 Nov 2000 [Voted into WP at October, 2009 meeting.]
Must a constructor for an abstract base class provide a
mem-initializer for each virtual base class from which it is directly
or indirectly derived? Since the initialization of virtual base
classes is performed by the most-derived class, and since an abstract
base class can never be the most-derived class, there would seem to be
no reason to require constructors for abstract base classes to
initialize virtual base classes.
It is not clear from the Standard whether there actually is such a
requirement or not. The relevant text is found in 12.6.2
[class.base.init] paragraph 6:
(...quoted above)
This paragraph requires only that the most-derived class's constructor
have a mem-initializer for virtual base classes. Should the silence be
construed as permission for constructors of classes that are not the
most-derived to omit such mem-initializers?
There is no "silence". The general rule applies as there is no specific rule for abstract classes.
Christopher Lester, on comp.std.c++, March 19, 2004: If any of you
reading this posting happen to be members of the above working group,
I would like to encourage you to review the suggestion contained
therein, as it seems to me that the final tenor of the submission is
both (a) correct (the silence of the standard DOES mandate the
omission) and (b) describes what most users would intuitively expect
and desire from the C++ language as well.
The suggestion is to make it clearer that constructors for abstract
base classes should not be required to provide initialisers for any
virtual base classes they contain (as only the most-derived class has
the job of initialising virtual base classes, and an abstract base
class cannot possibly be a most-derived class).
The suggestion cannot make "clearer" something that doesn't exist now.
Some committee members are taken their desire for reality and it is very wrong.
(snip example and discussion similar to OP's code)
Proposed resolution (July, 2009):
Add the indicated text (moved from paragraph 11) to the end of 12.6.2
[class.base.init] paragraph 7:
...The initialization of each base and member constitutes a
full-expression. Any expression in a mem-initializer is evaluated as
part of the full-expression that performs the initialization. A
mem-initializer where the mem-initializer-id names a virtual base
class is ignored during execution of a constructor of any class that
is not the most derived class.
Change 12.6.2 [class.base.init]
paragraph 8 as follows:
If a given non-static data member or base class is not named by a
mem-initializer-id (including the case where there is no
mem-initializer-list because the constructor has no ctor-initializer)
and the entity is not a virtual base class of an abstract class (10.4
[class.abstract]), then
if the entity is a non-static data member that has a
brace-or-equal-initializer, the entity is initialized as specified in
8.5 [dcl.init];
otherwise, if the entity is a variant member (9.5 [class.union]), no
initialization is performed;
otherwise, the entity is default-initialized (8.5 [dcl.init]).
[Note: An abstract class (10.4 [class.abstract]) is never a most
derived class, thus its constructors never initialize virtual base
classes, therefore the corresponding mem-initializers may be omitted.
—end note] After the call to a constructor for class X has
completed...
Change 12.6.2 [class.base.init] paragraph 10 as follows:
Initialization shall proceed proceeds in the following order:
First, and only for the constructor of the most derived class as
described below (1.8 [intro.object]), virtual base classes shall be
are initialized in the order they appear on a depth-first
left-to-right traversal of the directed acyclic graph of base classes,
where “left-to-right” is the order of appearance of the base class
names in the derived class base-specifier-list.
Then, direct base classes shall be are initialized in declaration
order as they appear in the base-specifier-list (regardless of the
order of the mem-initializers).
Then, non-static data members shall be are initialized in the order
they were declared in the class definition (again regardless of the
order of the mem-initializers).
Finally, the compound-statement of the constructor body is executed.
[Note: the declaration order is mandated to ensure that base and
member subobjects are destroyed in the reverse order of
initialization. —end note]
Remove all normative text in 12.6.2 [class.base.init] paragraph 11,
keeping the example:
All subobjects representing virtual base classes are initialized by
the constructor of the most derived class (1.8 [intro.object]). If the
constructor of the most derived class does not specify a
mem-initializer for a virtual base class V, then V's default
constructor is called to initialize the virtual base class subobject.
If V does not have an accessible default constructor, the
initialization is ill-formed. A mem-initializer naming a virtual base
class shall be ignored during execution of the constructor of any
class that is not the most derived class. [Example:...
The DR is marked "CD2": the committee agrees this was an issue and the language definition is changed to fix this issue.