众所周知std::unique_ptr
可能不方便用于实现 pimpl 习语:一个人可能不是默认析构函数和移动运算符在头文件中(例如,std::unique_ptr with an incomplete type won't compile)。有些人建议使用 std::shared_ptr
相反,因为它在析构函数中使用了一些技巧来克服它(可能只是类型删除,但我不确定)。
我试图为这种情况创建一个特殊的智能指针,这是实现:
#include <utility>
#include <type_traits>
template <class>
class PimplPtr;
template <class T, class... Args>
PimplPtr<T> MakePimplPtr(Args&&... args);
template <class T>
class PimplPtr {
static_assert(std::is_class_v<T>, "PimplPtr is only intented for use with classes");
template <class S, class... Args>
friend PimplPtr<S> MakePimplPtr(Args&&... args);
public:
PimplPtr() = default;
PimplPtr(const PimplPtr&) = delete;
PimplPtr(PimplPtr&& other) {
ptr_ = other.ptr_;
other.ptr_ = nullptr;
dest_caller_ = other.dest_caller_;
}
PimplPtr& operator=(const PimplPtr&) = delete;
PimplPtr& operator=(PimplPtr&& other) {
Reset();
ptr_ = other.ptr_;
other.ptr_ = nullptr;
dest_caller_ = other.dest_caller_;
}
~PimplPtr() {
Reset();
}
void Reset() {
if (!ptr_) {
return;
}
// first call the destructor
dest_caller_(ptr_);
// then free the memory
operator delete(ptr_);
ptr_ = nullptr;
}
T* operator->() const {
return ptr_;
}
T& operator*() const {
return *ptr_;
}
private:
explicit PimplPtr(T* ptr) noexcept
: ptr_(ptr), dest_caller_(&PimplPtr::DestCaller) {
}
static void DestCaller(T* ptr) {
ptr->~T();
}
using DestCallerT = void (*)(T*);
// pointer to "destructor"
DestCallerT dest_caller_;
T* ptr_{nullptr};
};
template <class T, class... Args>
PimplPtr<T> MakePimplPtr(Args&&... args) {
return PimplPtr{new T(std::forward<Args>(args)...)};
}
或者,可以用类型删除替换指向函数的指针,但我认为它的效率较低。
有效:
class PimplMe {
public:
PimplMe();
// compiles
PimplMe(PimplMe&&) = default;
~PimplMe() = default;
private:
class Impl;
PimplPtr<Impl> impl_;
};
我看到的唯一缺点是涉及的额外开销很少:还必须存储指向“析构函数”的指针。
我认为这不是什么大问题,因为 8 字节的开销在 pimpl 用例中微不足道,我的问题纯粹是出于兴趣:是否有一些实用的技巧可以消除由 dest_caller_
引起的空间开销?
我能想到拆分PimplPtr
进入申报pimpl.hpp
和定义pimpl_impl.hpp
,并显式实例化 template PimplPtr<PimplMe::Impl>::Reset()
在impl.cpp
,但我认为它很丑。
声明dest_caller_
作为静态成员不是解决方案,至少因为它需要在多线程情况下进行同步。
最佳答案
One may not default destructor and move operator right in the header file
解决方案是在源文件中直接默认它们。
虽然如何使用唯一指针实现 PIMPL 可能并不明显,但这当然不是不可能,通过编写可重用的模板,可以方便地重复不明显的部分。
我过去写过以下内容;我还没有检查最新的标准版本是否提供了一种简化它的方法:
// pimpl.hpp (add header guards of your choice)
#include <memory>
template <class T>
class pimpl {
public:
pimpl(pimpl&&);
~pimpl();
template <class... Args>
pimpl(Args&&...);
T* operator->();
const T* operator->() const;
T& operator*();
const T& operator*() const;
private:
std::unique_ptr<T> m;
};
// pimpl_impl.hpp (add header guards of your choice)
#include <utility>
#include "pimpl.hpp"
template <class T>
pimpl<T>::pimpl(pimpl&&) = default;
template <class T>
pimpl<T>::~pimpl() = default;
template <class T>
template <class... Args>
pimpl<T>::pimpl(Args&&... args) : m{new T{std::forward<Args>(args)...}} {}
template <class T>
T* pimpl<T>::operator->() {
return m.get();
}
template <class T>
const T* pimpl<T>::operator->() const {
return m.get();
}
template <class T>
T& pimpl<T>::operator*() {
return *m.get();
}
template <class T>
const T& pimpl<T>::operator*() const {
return *m.get();
}
// usage.hpp (add header guards of your choice)
#include "pimpl.hpp"
struct my_class {
my_class();
~my_class();
private:
pimpl<struct my_impl> m;
};
// usage.cpp
#include "usage.hpp"
#include "pimpl_impl.hpp"
struct my_impl {};
my_class::my_class() = default;
my_class::~my_class() = default;
关于c++ - 实现 pimpl 友好的 unique_ptr,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57403349/