3

The following question brought this phenomenon to notice where the constructor is being called even in the private mode of inheritance.

I tried it on the Diamond Problem, then made it simpler by breaking the diamond rule and just keeping virtual inheritance.

Further followed this up by a simple example of a 3-level inheritance which I am showing below (C inherited by B and B inherited by C - both in private inheritance) and still the constructor of A is being called.

Shouldn't A() be private and inaccessible in C?

#include<iostream>
using namespace std;
class A
{
public:
     A(){ cout << "1"; }
};

class B:  A
{
public:
    B(){ cout << "2"; }
};

class C:  B
{
public:
   C(){ cout << "3"; }
};


int main()
{
   C c1;
}

Output: 123 (You can also view the code and output here)

P.S: I have tried it both with the normal case (given here) and with virtual inheritance, even with the "Diamond-Problem" - the answer is same every time.

Failed Scientist
  • 1,977
  • 3
  • 29
  • 48

1 Answers1

3

There's no virtual inheritance involved, so it's not up to C to construct A. Therefore the constructor for C invokes the constructor for B, which in turn invokes the constructor for A. There is no supposed breach of access specifiers going on.

For the virtual inheritance case, we note that construction of sub-objects that are omitted from the member initialization list is specified by

[class.base.init/9]

In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

  • ...
  • ...
  • otherwise, the entity is default-initialized.

So the above clause would seem to indicate the default initialization must happen, with access specifiers supposedly discarded.

What's important to remember is that access specifiers are to limit what programs may refer to inside a class definition, they will not stop the compiler from emitting correct code, such as required by the clause above.

If however, you had attempted to explicitly call the default constructor for the virtual base, it would be ill-formed, like this:

#include<iostream>
using namespace std;
class A
{
public:
     A(){ cout <<"1";}
};
 
class B:  virtual A
{
public:
    B(){cout <<"2";}
};
 
class C:  B
{
public:
   C() : A() {cout<<"3";}
};
 
int main()
{
   C c1;
}

Which gives:

prog.cpp: In constructor ‘C::C()’:
prog.cpp:18:10: error: ‘class A A::A’ is inaccessible within this context
    C() : A() {cout<<"3";}
          ^
prog.cpp:4:1: note: declared here
 {
 ^

Why? Because now it is your program that is trying to breach access explicitly, and not the compiler doing its job.

Community
  • 1
  • 1
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • @TalhaIrfan - Your question is about the simpler case, so my answer is appropriately about the simpler case. If you want to know about virtual inheritance, make it more than a simple remark. – StoryTeller - Unslander Monica Apr 30 '17 at 06:10
  • Last line is gold!! Thanks a lot! – Failed Scientist Apr 30 '17 at 06:27
  • @TalhaIrfan - Happy to help – StoryTeller - Unslander Monica Apr 30 '17 at 06:27
  • Just one confusion! Why does it work when both B and C are virtual? – Failed Scientist Apr 30 '17 at 06:28
  • @TalhaIrfan - What do you mean? It still won't work even if C inherits virtually. You can't explicitly access A http://ideone.com/MbpbWz – StoryTeller - Unslander Monica Apr 30 '17 at 06:30
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143002/discussion-between-talha-irfan-and-storyteller). – Failed Scientist Apr 30 '17 at 06:32
  • Access specifiers generally DO prevent the compiler from generating code, that's how we built non-copyable classes before `= delete` was introduced. – Ben Voigt May 07 '17 at 05:20
  • @BenVoigt - I'd say it's the omission of a definition for the copy c'tor that stopped the copying, as opposed to the access specifier. But access specifiers won't stop a compiler from generating code if there's no explicit attempt to access a name. Consider [this proxy object example](http://ideone.com/Ufj90M). The type name isn't accessible, but C++11 doesn't stop you from defining objects of that type unless you take extra precautions. – StoryTeller - Unslander Monica May 07 '17 at 05:46
  • @StoryTeller: One small change to your example: http://ideone.com/NqRZGv – Ben Voigt May 07 '17 at 05:50
  • BTW, the compiler has never had any way to diagnose "omission of a definition for the copy constructor". It could be defined in another compilation unit, which wouldn't be discovered until link time. – Ben Voigt May 07 '17 at 05:52
  • @BenVoigt - Precisely the step required. But it isn't the plain access specifier to the type that stops it, after all. And linkage is also part of translation, is it not? I'm asking frankly, because I always considered it as such, and would like to know if my view diverges from what the industry thinks. – StoryTeller - Unslander Monica May 07 '17 at 05:55
  • "linkage" is the property of a name that determines whether it is visible outside the compilation unit. Thus the compiler produces it and the linker consumes it. Considering the linking step as part of overall program build, sure... but although the Standard never requires the steps to be separate, all the rules for diagnostics are written specifically to facilitate independent compilation and a separate link step. And noncopyable (whether boost or hand-rolled) most certainly resulted in a compile error, due to access specification. The link error caught copy attempts from inside the class – Ben Voigt May 07 '17 at 05:59