c++ - 实现 pimpl 友好的 unique_ptr

标签 c++ pimpl-idiom template-instantiation

众所周知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/

相关文章:

c++ - 在 emacs 中将 .h 设置为 c++ 模式组

c++ - 为什么结构数组的大小在传递给函数时会发生变化?

qt - QImage 写入时复制

c++ - 继承期间 protected 成员的 pimpl

c++ - 生成代码以实例化具有不同参数的函数模板

c++ - 如果特化已经被隐式实例化,它是否被隐式实例化?

c++ - 是否有显式实例化深层模板类的技巧?

c++ - 关于指针的有趣问题..请帮忙

c++ - 使用通用比较器进行字符串比较

c++ - 需要解决限制 : abstract class cannot be used for return types