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?
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.