-3

The title is only one of a few things I am confused on about the following example:

struct A {
    A() {
        std::cout << "Default constructor of A" << std::endl;
    }
    A(const A &) {
        std::cout << "Copy constructor of A" << std::endl;
    }
};

struct B : private A {
    // using A::A; // does not help for question 2.    
};

int main() {
    A a;
    B b;
    B c(b); // Does not work with `a` as an argument
    return 0;
}

This example outputs:

Default constructor of A
Default constructor of A
Copy constructor of A

Questions:

  1. How come the privately inherited constructors of B are available in main? This question is similar to the one in this post, but there the question was about using that constructor from inside of B, which is different.
  2. The copy constructor that gets called takes an const A & argument. However, if I write B c(a) instead of B c(b), the code does not compile. How come? (Please note that un-commenting the using directive in B does not help).
  3. This is minor, but still. How come the compiler does not warn me about unused variables a and c?

Question 2 has been taken to a different post.

Community
  • 1
  • 1
AlwaysLearning
  • 7,257
  • 4
  • 33
  • 68

2 Answers2

2
  1. You are not calling the constructor of A directly, the default constructor of B is doing that for you. If this worked any differently you would never be able to construct any class that inherits anything privately.

  2. This is because B has no copy constructor for type A. The only constructor that could apply here is the (default) copy constructor of type B, which takes a B as an argument.

Constructors are bound to their class, they do not inherit in the same sense as functions do. How to explain... The basic goal is to ensure that each class always constructs completely. Thus, to construct an A of any kind (whether standalone, or as part of B), a constructor of A must run. Similarly, to construct an object of class B, a constructor of B must run. If you 'inherit' the A::A() constructor, you would be able to construct B's that were not completely constructed. In that case the A::A() constructor has not run any part of the construction sequence for B, leaving B in an invalid state.

Let's try some different source. We keep A as it is, and change B like this:

struct B : private A {
    B () { val = 42; }
    void foo () { if (val != 42) abort (); }

    using A::A;

    int val;
};

Now let's say we, uhh, acquire a B without constructing it:

B b (a); // illegal, but for the sake of argument.
b.foo ();

We have specified that we construct this B using the A::A constructor, so the only code that will be executed is A::A(). In particular, B::B() is not executed, so val will have whatever value was present on the stack at the time. The chance of it being 42 is 1 in 2^32, in other words, not very likely.

What will happen when B.foo() is called? The object is not in a valid state (val is not 42), so the application aborts. Oops!

This is of course a contrived example, but it shows that using a non-constructed object is a very bad thing, and therefore the language prevents you from creating any such objects.

  1. They are not unused. There is all sorts of activity taking place in the constructors (the writing to cout), which cannot simply be eliminated.

This design pattern is seen many times in C++, for example in std::mutex. Just declaring a lock is sufficient to do the locking and unlocking, but there is no need to refer to the lock after the declaration. Since it is a common design pattern a warning would be inappropriate.

Only if the compiler can prove that constructing and destructing a local variable has no side effects, and finds that it is not being used, you may see a warning (depending on the compiler).

H. Guijt
  • 3,325
  • 11
  • 16
  • First, thank you for answering the question. In 2: I get the same error even if I have `using A::A` in `B`, which should make the copy constructor taking `const A &` available. In 3: I am quite sure that the compiler usually complains about objects that get constructed, but not used and I have to write something like `(void)a` to get it compiled (I compile with `g++` with `-pedantic -Werror`). – AlwaysLearning Jul 31 '16 at 14:03
  • Thank you for the addition, but still, what happens when I un-comment that `using` declaration in `B`? Why does is not make calling the constructor with `const A &` legal? – AlwaysLearning Jul 31 '16 at 14:14
  • Because B needs to be constructed as well. A::A() _only_ constructs A. Someone has to go and make sure B is also in a valid state before we can continue. Thus, a constructor for B _must_ run; just calling the A constructor is insufficient. – H. Guijt Jul 31 '16 at 14:17
  • Do you mean that the constructors included by the `using` statement are also called indirectly by the constructors of `B` and cannot be used directly? – AlwaysLearning Jul 31 '16 at 14:26
  • Essentially, yes. Constructors (and destructor) are special in various ways, including this. – H. Guijt Jul 31 '16 at 14:30
  • Here is an example that I think shows that this is not so: http://ideone.com/Lc4RAA. Here `B` does not have a constructor taking an `int`, but we can still create a `B` object using an `int` as an argument. Why is `const A &` different? – AlwaysLearning Jul 31 '16 at 14:41
  • I think that it's time to take this to a different post: http://stackoverflow.com/q/38685216/2725810 – AlwaysLearning Jul 31 '16 at 14:56
1

How come the privately inherited constructors of B are available in main?

They're not.

They are, however, available to B itself because they have been, well, inherited, and it's B that invokes the inherited base constructors.

Obviously, if this weren't so, then private inheritance would be utterly useless because you'd never be able to instantiate a privately inheriting class.

They get called and the output is the proof of that

No, it's not. You're jumping to conclusions. There are intermediate function calls (implicitly-defined constructors in B) that produce no output that you have failed to consider.

The copy constructor that gets called takes an const A & argument. However, if I write B c(a) instead of B c(b), the code does not compile. How come?

No, it doesn't. Your line of code invokes the copy constructor in B that takes a const B& argument. B doesn't have a copy constructor that takes a const A& argument.

That copy constructor in turn internally invokes the base's copy constructor, producing the output you see.

Constructors are not inherited in the manner you seem to believe.

This is minor, but still. How come the compiler does not warn me about unused variables a and c?

Because they're not unused. Their construction did a thing (created output).

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055