1

According to this question it is possible to forward declare smart pointers if all constructors and destructors are not inline (requiring fully defined types then). When no destructor is provided, the compiler will declare one and provide an inline definition which then requires the type in the smart pointer to be fully known in the header. The same applies for default constructors.

However, I have found out that it also applies for inherited constructors as well and it is a bit confusing for me. Consider:

class Base
{
public:
    Base(); //defined in cpp
};

class SomeClass;

class Derived : public Base
{
    using Base::Base;
    ~Derived(); //defined in cpp

    std::unique_ptr<SomeClass> ptr;
};

This will not compile unless the Derived constructor is declared explicitly and defined only in the source file. Why? The Base constructor is not inline and as far as I know the using directive should cause "inheritance" of the constructors in similar fashion as other members. Or does the compiler interpret it as "declare for me the same constructors as in Base and define them inline"?

Community
  • 1
  • 1
Resurrection
  • 3,916
  • 2
  • 34
  • 56

3 Answers3

4

First let's reproduce the problem in the smallest amount of code:

#include <memory>

class SomeClass;

int main()
{
  std::unique_ptr<SomeClass> ptr;
}

error:

In file included from /opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/memory:81:0,
from <source>:1:
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = SomeClass]':
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:236:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = SomeClass; _Dp = std::default_delete<SomeClass>]'
<source>:7:30:   required from here
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:74:22: error: invalid application of 'sizeof' to incomplete type 'SomeClass'
static_assert(sizeof(_Tp)>0,
^
Compiler exited with result code 1

Same problem here (to prove that it's nothing to do with inheritance):

#include <memory>

class SomeClass;

class NotDerived
{
//    ~NotDerived(); //defined in cpp

    std::unique_ptr<SomeClass> ptr;
};

int main(){
  NotDerived d;
}

error:

In file included from /opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/memory:81:0,
from <source>:1:
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = SomeClass]':
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:236:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = SomeClass; _Dp = std::default_delete<SomeClass>]'
<source>:5:7:   required from here
/opt/gcc-explorer/gcc-6.2.0/include/c++/6.2.0/bits/unique_ptr.h:74:22: error: invalid application of 'sizeof' to incomplete type 'SomeClass'
static_assert(sizeof(_Tp)>0,
^
Compiler exited with result code 1

Now let's remember what unique_ptr really is:

template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

And default_delete ...

Calls delete on ptr

And delete on the pointer (to SomeClass) will want to destruct the Someclass, so it will need to call SomeClass::~SomeClass

Which you have not yet declared. Hence the error.

Why is this relevant?

Because in your code, if you don't declare a destructor for Derived, a default one is generated, which of course will call the destructor of ptr.

At this point, the compiler will need a complete definition of SomeClass so that it knows how to destroy it.

By declaring the destructor in Derived, you are deferring this problem to the imlementation of Derived::~Derived.

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • You have described what the answer I link to is about but you have not addressed my question - why inherited constructors prevent forward-declared types in smart pointer members. The answer provided by Martin Bonner is correct - because compiler implicitly defines the inherited constructors like the default constructors (inline) hence the issue. – Resurrection Dec 11 '16 at 13:49
  • @MichaelVlach you'd find the same problem if `Derived` was not a derived class. It's nothing to do with inheritance. – Richard Hodges Dec 11 '16 at 14:20
  • I would not find the same problem because without inheriting another class you cannot possibly inherit constructors. What the question is about is even mentioned in the title... – Resurrection Dec 11 '16 at 14:22
  • @MichaelVlach thought the question was about the need to declare `Derived`'s destructor? – Richard Hodges Dec 11 '16 at 14:23
  • Why would you think that? The question about the need to declare `Derived`'s destructor is the one I linked in my post. Whereas I myself asked about why I cannot use inherited constructors in this scenario. The title says as much. Maybe you meant to reply in that other question I linked? (it would fit there actually) – Resurrection Dec 11 '16 at 14:25
  • @MichaelVlach This was your question: "This will not compile unless the Derived constructor is declared explicitly and defined only in the source file. Why?" - I have given you the answer. The inherited constructors are nothing to do with it. – Richard Hodges Dec 11 '16 at 14:27
  • Yes, as opposed to inheriting constructor form the base class! Inheriting constructors have everything to do with it. Furthermore your answer concerns only destructors... – Resurrection Dec 11 '16 at 14:47
  • @Resurrection ah I see, I mistook your 'constructor' for 'destructor'. The answer is the same. The synthesised constructor needs a complete definition of `std::deleter` which needs a complete `SomeClass` at the point of invocation of its constructor. As you can see in the error message. – Richard Hodges Dec 11 '16 at 16:27
3

The last sentence is your answer. The compiler interprets using Base::Base; as

"I want Derived to have constructors with the same set of signatures as Base has. Please define them for me."

  • 2
    And it then follows the same rules as for other implicitly declared constructors/destructors making the definitions inline by default. I guess there is no easy way around it (having inherited constructors but let compiler define them somewhere else instead inlining them in the header). – Resurrection Dec 11 '16 at 13:16
0

For Derivated you did not declare a constructor, so the default one is created, that is inline.

The fact that the default constructor ,or any other constructor, calls the base constructor is another topic that has nothing to do with the Base constructor that is not inline.

Basicaly there is no connection in C++ between the constructor of your class and the one in the base class, except for the fact that the one in the base class is executed before the one in the derivated class, and, if not explicitly stated, the default one from the base class will be called.

Dragos Pop
  • 428
  • 2
  • 8
  • I do not think what you say is correct. When a class inherits constructors they have clear and defined relation to the base class' constructors. See http://en.cppreference.com/w/cpp/language/using_declaration#Inheriting_constructors although that link is not entirely correct either. For example it states that for `struct A { A(int); }` any derived class will have implicitly declared default constructor `struct B : public A { using A::A; }` but that is not true. The constructor is declared but is implicitly deleted (VS 2015). The problem is the way compiler defines inherited constructors. – Resurrection Dec 11 '16 at 13:32