Search code examples
c++inheritancedestructor

std::vector, incomplete type and inherited constructors


I have a class with a vector member variable whose T is declared, but not defined. This can be a problem if no destructor for the class is defined, since the compiler might pick some other translation unit to generate the destructor in. If that TU doesn't have a definition for T, compilation fails.

struct undefined;

struct S
{
    S(int);
    std::vector<undefined> v;
};

int main()
{
   S s(42);     // fails in ~vector(), `undefined` is undefined
}

To work around this problem, I usually add a defaulted destructor as an anchor in the file that implements the class, which has access to the definition.

struct undefined;

struct S
{
    S(int);
    ~S();       // implemented in another TU as `S::~S() = default;`
    std::vector<undefined> v;
};

int main()
{
   S s(42);     // ok
}

I'm trying to do the same thing with a derived class that inherits the base class' constructor:

#include <vector>

struct undefined;

struct base
{
   base(int);
};

struct derived : base
{
   using base::base;
   ~derived();
   std::vector<undefined> v;
};


int main()
{
   derived d(1);  // fails in ~vector(), `undefined` is undefined
}

If I change derived to not inherit the constructor, compilation succeeds:

#include <vector>

struct undefined;

struct base
{
   base(int);
};

struct derived : base
{
   //using base::base;           <--- note
   derived(int);
   ~derived();
   std::vector<undefined> v;
};


int main()
{
   derived d(1);  // ok
}

What's really confusing to me is that clang says this:

stl_vector.h:336:35: error: arithmetic on a pointer to an incomplete type 'undefined'

[blah]

a.cpp:12:14: note: in instantiation of member function 'std::vector<undefined>::~vector' requested here
        using base::base;
                    ^

It sounds like using base::base is also bringing in something that's destroying the vector, but I have no idea what. I've tried deleting copy/move constructors/operators in both classes, but the error remains. Both g++ and Visual C++ fail with similar errors.

What's going on?


Solution

  • As mentioned by Raymond Chen in this comment, the reason using base::base requires the vector's destructor is because exceptions may be thrown from the constructor, which may require destroying member variables. The only solution is to write the constructor manually.

    A constructor needs to destroy fully constructed member variables if an exception is thrown. In this example, a must be destroyed, but not b:

    std::string throws()
    {
        throw 1;
    }
    
    struct S
    {
        std::string a, b;
    
        S() : a(""), b(throws()) {}
    };
    

    In the next example, the compiler generates a default constructor and a destructor. Both can call ~vector() and so both need the definition of incomplete:

    struct incomplete;
    
    struct S
    {
        std::vector<incomplete> v;
    };
    
    int main()
    {
        S s;  // error
    }
    

    Adding a declaration for both fixes the error:

    struct incomplete;
    
    struct S
    {
        std::vector<incomplete> v;
        S();
        ~S();
    };
    
    int main()
    {
        S s;   // ok
    }
    

    But an inherited constructor is also generated by the compiler and therefore needs ~vector() in case an exception is thrown:

    struct undefined;
    
    struct base
    {
        base(int);
    };
    
    struct derived : base
    {
        std::vector<undefined> v;
    
        using base::base;   // <- needs ~vector() in case of exceptions
        ~derived();
    };
    

    And so the only solution is to declare the constructor manually and implement it somewhere else.