Tomas Dittmann Tomas Dittmann - 1 year ago 53
C++ Question

incomplete type as member of std::map

I came about the same issue as described here

Can't allocate class with forward declared value in std::map member variable

in our codebase.

Hoever I also found other cases where our compiler (MSVC 2017) is able to compile this...

After fiddling around with the code I found that defining the con- & destructor in the cpp allows the files to compile.

In

test.h
:

#ifndef TEST_H
#define TEST_H

#include <map>
struct Incomplete;

class Test {
std::map<int, Incomplete> member;
public:
Test();
~Test();
int foo() { return 0; }
};

#endif


In
test.cpp
:

#include "test.h"
struct Incomplete {};
Test::Test() {}
Test::~Test() {}


In
main.cpp
:

#include "test.h"

int main()
{
Test test;
return test.foo();
}


Why does defining the con- & destructor in the cpp file allow member-std::map-variables to use incomplete types?


Answer Source

This is because declaring the class member does not require Incomplete to be complete, but invoking the std::map destructor does, because it necessarily needs to invoke the Incomplete destructor to destroy the map contents. (Invoking the default std::map constructor could require the type to be complete, depending on the implementation. I'm not sure if the spec puts any requirements on this. I can think of at least one implementation that would not require complete types.)

If you rely on the compiler to generate implicit ctors/dtors for you, that means that the type must be complete when the class definition is encountered, because that's when the compiler is going to implicitly generate the ctor and dtor. It's as though you wrote inline Test::Test() {} inline Test::~Test() {} immediately following the class definition. The dtor is implicitly going to destroy the map, which is going to destroy the map contents by calling ~Incomplete() on any stored values, which we can't do without a definition for Incomplete. And there, the whole thing falls apart and you get an error.

However, if you tell the compiler (by way of the Test ctor/dtor declarations) that you will be implementing them later, then it won't generate them, therefore no std::map ctor/dtor invocation gets compiled at that point.

Then you complete the Incomplete type prior to defining the ctor/dtor yourself, so the Incomplete ctor/dtor invocations can be successfully compiled. If you remove the definition of Incomplete then you will run into the same error.


Note that, as others have said, you can side-step this issue by storing pointers/references to the incomplete type in the map instead. A pointer or reference to an incomplete type is actually itself a complete type. However, this may not be desirable in all cases so I'm hesitant to push that solution without knowing more details about how the map will be used.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download