实施 pimpl 友好型unique_ptr
Implementing pimpl-friendly unique_ptr
众所周知,std::unique_ptr
可能不方便地用于实现 pimpl 习语:不能默认析构函数并在头文件中移动运算符(例如,不完整类型的 std::unique_ptr 不会编译(。有些人建议改用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_;
};
我看到的唯一缺点是涉及少量的额外开销:还必须存储指向"析构函数"的指针。
我认为这不是什么大问题,因为在 pimpl 用例中 8 字节开销微不足道,我的问题纯粹令人感兴趣:是否有一些实用的技巧可以消除dest_caller_
造成的空间开销?
我可以想到将PimplPtr
拆分为声明pimpl.hpp
和定义pimpl_impl.hpp
,并在impl.cpp
中显式实例化template PimplPtr<PimplMe::Impl>::Reset()
,但我认为这很丑陋。
将dest_caller_
声明为静态成员不是解决方案,至少因为它在多线程情况下需要同步。
不能默认析构函数并在头文件中移动运算符
解决方案只是在源文件中默认它们。
虽然如何使用独特的指针实现 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;
相关文章:
- 为什么 std::unique 不调用 std::sort?
- CLANG 编译器 说:变量"PTR"可能未初始化
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 将 ptr 传递给 ptr 到 A 作为参数传递给 A 的函数是不好的做法吗?
- 为共享 ptr 向量实现复制 c'tor?
- 字符和整数中 **(ptr+1) 的值差异
- C++:在不中断共享的情况下通过引用传递共享 PTR?
- 生成"unique"矩阵
- 如何将派生类从基 ptr 分配给 nlohmann::json
- 引用 std::shared:ptr 以避免引用计数
- 我对 std::unique(算法)C++有问题
- 为什么我不能在不进行任何转换的情况下将浮点数放入任何类型的 ptr 中?
- 在调用函数时,ptr** 和 ptr*& 之间是否有区别,或者首选C++?
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- 尝试打印出 *ptr++ 的值,以了解它是如何工作的
- 如何控制共享 ptr 引用计数?
- std::shared_ptr::unique(),复制和线程安全
- 如何在C++03中用自定义谓词调用std::unique
- C++中的指针否定 (!ptr == NULL)
- C++14 unique_ptr并使用已删除的函数'std::unique-ptr' unique_ptr错误