C++/pimpl:原始指针还是unique_ptr?什么是更好的选择?

C++/pimpl: raw pointer or unique_ptr? What is a better choice?

本文关键字:ptr 什么 更好 选择 unique 原始 指针 C++ pimpl      更新时间:2023-10-16

当您将unique_ptr<T>用于前向声明的类型T时,unique_ptr析构函数要求T是完整的,但移动赋值运算符也(和reset),根据下表:

https://stackoverflow.com/a/6089065/1794803

因此,对于您的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.
};

这不是更好的解决方案吗?(+ 定义或删除你自己的 copy/move 运算符,如果你想类是可复制的/可移动的;但这是一个"有意识的选择";但是,不要为unique_ptr写移动作业是一个错误)。

使用unique_ptr只能节省您在析构函数中编写delete p_impl,无论如何都必须声明该析构函数。

unique_ptr是本地动态对象的绝佳选择,即使在异常情况下也会被破坏,但对于"属性",如果您不记得必须重写移动赋值运算符,您只能保存获得 UB 的可能性。

好吧,使用std::unique_ptr可以使您免于为p_impl的显式delete而烦恼。

此外,在并发访问和构造函数中的异常情况下,它应该可以很好地工作(使用原始指针和自己new似乎不能保证)。

std::unique_ptr 应该是 pimpl 根据的首选方式。有关参考,请参阅Herb Sutter在CppCon16上约10分钟的演讲。 原因是,它可以防止您在维护 RAII 时意外更换疙瘩。

在问题中,不需要定义移动分配运算符是期望的优势。下面我建议另一个解决方案,"也"具有这个优势,而且你不需要定义析构函数。我仍然认为以下解决方案最重要的优点是现在可以在匿名命名空间(或未命名命名空间)中定义实现。 您必须付出的代价是(可重用)基类的声明: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.