C++/pimpl:使用原始指针还是unique_ptr?哪个更好?

3
当你使用一个前置声明类型Tunique_ptr<T>时,unique_ptr析构函数需要T是完整的,但是移动赋值运算符(和reset)也是如此,根据这个表格:https://dev59.com/bm025IYBdhLWcg3wbVan#6089065 所以,对于你的pImpl惯用法,为了正确实现它,你必须声明deletemove assignment method(副作用是标记它们为非内联函数)。
class impl_t;

class A
{
   std::unique_ptr<impl_t> p_impl;

public:
   // Implement in A.cpp as A::~A() = default;
   ~A();

   // Implemented in A.cpp as A& operator=(A&&) = default;
   A& operator=(A&& he);
};

但是,由于std::unique_ptr是一种用于动态内存的RAII解决方案,而且您的pImpl已经在一个类中,您无论如何都必须编写一个析构函数,那么管理原始指针是否更好呢?因为从p_impl的角度来看,您的类已经类似于RAII:

class impl_t;

class A
{
    impl_t* p_impl;

public:
    ~A(); // The destructor must be written anyway.

    // The omitted move assignment destructor doesn't cause UB.
};

这是不是更好的解决方案?(+如果你想将类标记为可复制/可移动,请定义或删除自己的复制/移动运算符;但这是一个“明智的选择”;然而,为unique_ptr编写移动赋值是一个错误)。
仅使用unique_ptr只能使您在析构函数中写入delete p_impl,而您必须在任何情况下都要声明析构函数。 unique_ptr是局部动态对象的理想选择,即使出现异常也会被销毁,但对于“属性”,除了可能忘记重写移动赋值运算符并得到UB之外,没有任何节省。
3个回答

4

使用 std::unique_ptr 可以避免你为 p_impl 显式地写 delete,这样会让你的工作更加轻松。

同时,在构造函数中使用原始指针和自己手动申请内存 new 并不能保证在并发访问和异常情况下良好运行,但使用 std::unique_ptr 则不同。


此外,如果没有自定义删除器,std::unique_ptr 在优化后的大小方面与原始指针相当。 - skypjack

0

根据惯例,std::unique_ptr应该是pimpl的首选方式。有关参考,请参见Herb Sutter在CppCon16上的演讲,大约在10分钟左右。 原因是,它将防止您在维护RAII时意外更改pimpl。


0
在这个问题中,不需要定义移动赋值运算符被给予为期望的优势。下面我提出了另一种解决方案,它‘也’具有这个优势,而且你不需要定义析构函数。尽管如此,我认为下面解决方案最重要的优势是实现现在可以在匿名命名空间(或未命名命名空间)中定义。
你需要付出的代价是声明一个(可重用的)基类:impl_t
#include <iostream>
#include <memory>

////////////////////////
// a utility impl.h file
struct impl_t
{
    virtual ~impl_t() = 0;
};
inline impl_t::~impl_t() {}

///////////////////////
// the a_class.cpp file
class a_class_t
{
    std::unique_ptr<impl_t> m_pimpl;
public:
    a_class_t();
    int a_method(int);
};

///////////////////////
// the a_class.cpp file
namespace { // anonymous

struct a_class_impl_t
    : impl_t
{
    int a_method(int x)
    {
        return x + 1;
    }
    ~a_class_impl_t()
    {
        std::cout << "~a_class_impl_t()\n";
    }
};

} // anonymous namespace

int a_class_t::a_method(int x)
{
    return dynamic_cast<a_class_impl_t&>(*m_pimpl).a_method(x);
}

a_class_t::a_class_t()
    : m_pimpl(std::make_unique<a_class_impl_t>())
{}

////////////////////
// the main.cpp file
int main()
{
    a_class_t a_class;
    std::cout << a_class.a_method(1) << '\n';
}

而且这个解决方案最好使用 unique_ptr


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接