Patrick Patrick - 16 days ago 5
C++ Question

Why does default move constructor need default-deleter of class used in unique_ptr?

The following code correctly compiles with Visual Studio 2013:

#include <memory>

namespace NS
{
class SomeOtherClass;

class MyClass
{
public:
MyClass();
virtual ~MyClass();
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}

int main()
{
auto mc = NS::MyClass();
}


This is because of a bug in Visual Studio 2013, in which the initialization of
mc
in
main
is directly optimized without checking for a move constructor.

In Visual Studio 2015 this doesn't compile because the move constructor must exist, so we change the code to this:

#include <memory>

namespace NS
{
class SomeOtherClass;

class MyClass
{
public:
MyClass();
virtual ~MyClass();
MyClass(MyClass&&) = default;
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}

int main()
{
auto mc = NS::MyClass();
}


And again this compiles.

But, if we now want to export the DLL, then compilation again fails. This is the modified code:

#include <memory>

namespace NS
{
class SomeOtherClass;

class __declspec(dllexport) MyClass
{
public:
MyClass();
virtual ~MyClass();
MyClass(MyClass&&) = default;
private:
std::unique_ptr<SomeOtherClass> m_someOtherClass;
};
}

int main()
{
auto mc = NS::MyClass();
}


This is a part of the compiler output:

memory(1193): error C2027: use of undefined type 'NS::SomeOtherClass'
test.cpp(5): note: see declaration of 'NS::SomeOtherClass'
...
memory(1194): error C2338: can't delete an incomplete type
memory(1195): warning C4150: deletion of pointer to incomplete type 'NS::SomeOtherClass'; no destructor called


It seems that the default-generated move-constructor requires being able to destruct
SomeOtherClass
. This is strange because
MyClass
has a destructor, in which the full definition of
SomeOtherClass
is known.

So why doesn't this compile when exporting the DLL? And why does the default move constructor require knowing the definition of
SomeOtherClass
?

Answer

std::unique_ptr requires a complete type, specifically to handle the deletion.

Your defaulted move constructor is inline and could be represented by the following pseudocode:

MyClass(MyClass&& other) {
    m_someOtherClass.reset(other.m_someOtherClass.release());
}

This requires SomeOtherClass to be a complete type, to be able to release currently held m_someOtherClass. If you try to actually use the move constructor in your example, you'll get the same error.

MSDN on defining inline C++ functions with dllexport

You can define as inline a function with the dllexport attribute. In this case, the function is always instantiated and exported, whether or not any module in the program references the function. The function is presumed to be imported by another program.

I don't have VS2015 handy, but just declaring the constructor in the class and defining it in a translation unit where SomeOtherClass is defined should do the trick:

   class __declspec(dllexport) MyClass
      {
      public:
         MyClass();
         virtual ~MyClass();
         MyClass(MyClass&&);
      private:
         std::unique_ptr<SomeOtherClass> m_someOtherClass;
      };
   }

file_containing_~MyClass.cpp

MyClass::MyClass(MyClass&&)=default;
Comments