I've come across a case in which type-safe c++ produces non-matching ctor/dtors. The following code generates two constructors for A. The default constructor also constructs its base (B), but the default-generated copy/move ctor does not construct B. Later, it destroys B, so we get non-matching ctor/dtor.
I've tried this with gcc and clang and both fail. In the gcc bug report forum they suggested this isn't a gcc problem. I may be missing something, but isn't there something odd when type-safe code results in a dtor call for a class that hasn't been constructed?
B() -> INSERT: 0x7fff55398b2f
~B() -> ERASE: 0x7fff55398b2f
~B() -> ERASE: 0x7fff55398b40 // <- unmatched dtor call
Assertion failed: (!all.empty()), function ~B, file gcc_bug.c, line 20.
Code follows:
#include <set>
#include <iostream>
#include <cstdint>
#include <cassert>
#include <experimental/optional>
std::set<std::uintptr_t> all;
struct B
{
B()
{
std::cerr << "B() -> INSERT: " << this << "\n";
all.insert((std::uintptr_t)this);
}
~B()
{
std::cerr << "~B() -> ERASE: " << this << "\n";
assert(!all.empty()); // FAILS
assert(all.find((std::uintptr_t)this) != all.end()); // FAILS
all.erase((std::uintptr_t)this);
}
};
struct A : B {};
static std::experimental::optional<A> f()
{
A a;
return a;
}
int main()
{
auto a = f();
return 0;
}
You have a B
being created by the implicitly defined copy constructor. This, of course, is not calling B::B()
. If you add the following constructor:
B(const B& other) : B()
{
*this = other;
}
you will see the output:
B() -> INSERT: 0x7ffe57ef918f
B() -> INSERT: 0x7ffe57ef91b0
~B() -> ERASE: 0x7ffe57ef918f
~B() -> ERASE: 0x7ffe57ef91b0
The important point to take away is this: every constructor completely constructs the object. By default, a copy constructor will not call the default constructor (and, obviously, vice versa). Therefore if you have something that needs to be done in every constructor, you must explicitly do that in every constructor, either by direct call, or by constructor chaining.