std::unique_ptr for class 数据成员 ABI(Pimpl 惯用语)

std::unique_ptr for class data member ABI (Pimpl idiom)

我正在尝试定义使用 "pimpl" 数据成员的现有代码,这些数据成员将被定义为 unique_ptr。有些对象需要自定义删除器,有些则不需要。

unique_ptr(不像shared_ptr)析构函数需要知道对象的完整类型。所以需要在数据成员声明中指定删除器:

class Foo {
public:
   ...
   ~Foo (void) //in source file  =default
private:
   class FooImpl;
   std::unique_ptr <FooImpl> _pimpl;
};

在实例化 pimpl 时,您只能使用默认删除器。如果要自定义删除器,需要在声明中指定

class Foo {
public:
   ...
   ~Foo (void) //in source file  =default
private:
   class FooImpl;
   std::unique_ptr <FooImpl,  std::function <void (FooImpl*&)> > _pimpl;
};

但是,无论您是想要具有默认行为的 unique_ptr d'tor 还是自定义删除器,您都无法灵活选择。更灵活的选项是第二个版本,但如果您选择保持默认行为,则必须使用等同于默认删除的特定删除器实例化 unique_ptr,例如:

Foo::Foo (void) :
    _impl (new CurveItemWidgetImpl, std::default_delete <FooImpl> ()) {
}

那么,std::unique_ptr 是处理 ABI 的最佳方式吗(与 shared_ptr 或原始指针相比)?

您可以轻松提供有效调用不透明符号的删除器:

class Foo {
public:
  ~Foo(); // = default
private:
  class FooImpl;
  struct FooImplDelete { void operator()(FooImpl*); };
  std::unique_ptr<FooImpl, FooImplDelete> _pimpl;
};

现在您可以将 Foo::FooImplDelete::operator() 的定义移动到您的源文件中。实际上,优化编译器会将其内联到 Foo.

的析构函数中

如果您没有特别的理由怀疑需要自定义删除器,您不妨使用默认值;如果您需要将自定义删除器更改为 releaseunique_ptr:

Foo::Foo() try
  : _pimpl(new FooImpl)
{
}
catch(...)
{
  delete _pimpl.release();
}

Foo::~Foo()
{
  delete _pimpl.release();
}

这里有 2 种兼容且可移植的方式。

这首先是简单地自己管理内存(你会发现这很简单)。第二个利用 unique_ptr

自行管理:

class Foo 
{
public:
    Foo();

    Foo(Foo&& rhs) noexcept;
    Foo(const Foo&) = delete;
    Foo& operator=(Foo&& rhs) noexcept;
    Foo& operator=(const Foo&) = delete;
    ~Foo() noexcept;
private:
    class impl;
    impl* _pimpl = nullptr;
};

// implementation:

class Foo::impl
{
    // TODO: implement your class here
};

// example constructor
Foo::Foo()
: _pimpl { new impl {} }
{}

Foo::Foo(Foo&& rhs) noexcept
: _pimpl { rhs._pimpl }
{
    rhs._pimpl = nullptr;
}

Foo& Foo::operator=(Foo&& rhs) noexcept
{
    using std::swap;
    swap(_pimpl, rhs._pimpl);
}

Foo::~Foo() noexcept
{
    // perform custom actions here, like moving impl onto a queue

    // or just delete it
    delete _pimpl;
}

使用 unique_ptr:

Foo.h:

#ifndef __included__foo__h__
#define __included__foo__h__

#include <memory>

class Foo 
{
public:
    Foo();
    ~Foo();
private:
    class impl;
    std::unique_ptr<impl, void (*) (impl*) noexcept> _pimpl;
    static void handle_delete(impl*) noexcept;
};


#endif

Foo.cpp:

#include "Foo.h"

class Foo::impl
{
    // TODO: implement your class here
};

// example constructor
Foo::Foo()
: _pimpl { new impl, &Foo::handle_delete }
{

}

Foo::~Foo() = default;

void Foo::handle_delete(impl* p) noexcept
{
    // do whatever you need to do with p

    // or just delete it
    delete p;
}