std::unique_ptr pimpl in dll generates C4251 with

2020-05-30 10:04发布

问题:

This is not a breaking issue but I like to clean my code from warnings so this is getting on my nerves.

I have been using the c++11 version of pimpl idiom to hide the class implementation for my library the usual way.

// dll header
class FrameworkImpl;

class EXPORT_API Framework
{
    Framework(const Framework&) = delete;
    Framework& operator=(const Framework&) = delete;
    Framework(Framework&&) = delete;
    Framework& operator=(Framework&&) = delete;

public:
    Framework();
    ~Framework();

private:
    std::unique_ptr<FrameworkImpl> impl_;
};

// application implementation
int main()
{
    std::unique_ptr<Framework> test = std::make_unique<Framework>();
}

Everything will be fine, but I will keep getting the warning:

warning C4251: 'Framework::impl_': class 'std::unique_ptr<FrameworkImpl,std::default_delete<_Ty>>' needs to have dll-interface to be used by clients of class 'Framework'

So I tried have to add:

template class EXPORT_API std::unique_ptr<FrameworkImpl>;

Before the forward declaration but the warning would just change to:

warning C4251: 'std::_Unique_ptr_base<_Ty,_Dx>::_Mypair': class 'std::_Compressed_pair<_Dx,FrameworkImpl *,true>' needs to have dll-interface to be used by clients of class 'std::_Unique_ptr_base<_Ty,_Dx>'

I have being seeing this issue since VS2010 and I cannot figure a good way to fix this. No problems on gcc or clang and it would break my heart to use the old raw pointer version..

回答1:

That is a very common issue with DLL classes, that use templates from std.

Why does it happen?

Reason is very simple: standard specifies only guarantees, limitations and requirements. So you can be sure, that every C++ 11 compiler will provide std::unique_ptr, that looks and works as described on this page. But everything else is implementation dependent.

The main problem is, that different implementations may (and usually, will) use a totally different structure for particular types. They use additional helper variables, different layout and so on. This may differ even between two versions of the same compiler. So if client code touches in any way member variables of your class, you need to provide DLL interface for them. That applies recursively to all types used by dllexported class.

You may want to read this article on MSDN, that describes this problem with containers in mind.

This problem can be simplified to the following:

  • If client code has no access to your data, disable this warning.
  • If you have members, that are intended to use by client code, create wrapper, that is dllexported or use additional indirection with dllexported methods.
  • Usually, you can use PIMPL to hide non-DLL types, but in your case it is not applicable, since you use non-exportable type to actually implement PIMPL.

Further reading:

  • MSDN: How to export an instantiation of a STL class
  • Microsoft DLL export and C++ templates
  • SO: Exporting classes containing std:: objects from a dll
  • SO: How to use an exported class in an STL template?


回答2:

Instead of exporting the entire class, you could export public methods only:

class Framework
{
    Framework(const Framework&) = delete;
    Framework& operator=(const Framework&) = delete;
    Framework(Framework&&) = delete;
    Framework& operator=(Framework&&) = delete;

public:
    EXPORT_API Framework();
    EXPORT_API ~Framework();

private:
    std::unique_ptr<FrameworkImpl> impl_;
};


回答3:

The solution was to declare the constructor/destructor after the impl declaration and a combination of Olga Perederieieva answer

Please refer to this website for a detailed explanation and sample

Header:

#include <memory>

class FridgeImpl;

class Fridge
{
public:
   DLL_EXPORT Fridge();
   DLL_EXPORT ~Fridge();
   DLL_EXPORT void coolDown();
private:
   std::unique_ptr<FridgeImpl> impl_;
};

Implementation:

#include "Engine.h"
#include "Fridge.h"

class FridgeImpl
{
public:
   void coolDown()
   {
      /* ... */
   }
private:
   Engine engine_;
};

Fridge::Fridge() : impl_(new FridgeImpl) {}

Fridge::~Fridge() = default;