Reputation: 23629
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
?
Upvotes: 4
Views: 496
Reputation: 16431
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(std::move(other.m_someOtherClass));
{}
This requires SomeOtherClass
to be a complete type, to be able to move the default deleter templated on it.
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;
Upvotes: 4