C++ 独特的ptr和转发声明:编写工厂函数的正确方法
最近我学习了智能PTR,我正在尝试编写一个返回唯一PTR的工厂函数。阅读了几篇关于将创建时间与显式定义的ctor和dtor放在同一个cpp文件中的文章后,我认为我可以这样做:C++ 独特的ptr和转发声明:编写工厂函数的正确方法,c++,c++11,smart-pointers,forward-declaration,C++,C++11,Smart Pointers,Forward Declaration,最近我学习了智能PTR,我正在尝试编写一个返回唯一PTR的工厂函数。阅读了几篇关于将创建时间与显式定义的ctor和dtor放在同一个cpp文件中的文章后,我认为我可以这样做: // factory.hpp struct Foo; std::unique_ptr<Foo> create(); 但是我得到了不完整的类型错误。然后经过几个小时的网络搜索和实验, 我意识到我甚至不能这样做: 这里有一个经典的独特的\u ptr Pimpl习惯用法 // A.hpp struct B;
// factory.hpp
struct Foo;
std::unique_ptr<Foo> create();
但是我得到了不完整的类型错误。然后经过几个小时的网络搜索和实验,
我意识到我甚至不能这样做:
这里有一个经典的独特的\u ptr Pimpl习惯用法
// A.hpp
struct B;
struct A {
A();
~A();
unique_ptr<B> b;
};
#包括“A.hpp”
int main(){
A;//这很好,因为我们正确地执行了Pimpl。
//现在,我不能这么做。
auto b=std::move(a.b);//当编译器实例化std::unique_ptr
的析构函数时,编译器必须找到Foo::~Foo()
并调用它。这意味着Foo
必须是std::unique_ptr
被销毁点的完整类型
这个代码很好:
struct Foo;
std::unique_ptr<Foo> create();
您似乎正在使用std::unique\u ptr
正确地实现pimpl。您必须在B
完成的位置(在cpp文件中)定义A::~A()
。您必须定义A::A()
位于同一位置,因为如果要分配内存并调用其构造函数,则必须完成B
所以这很好:
// a.hpp
struct A {
A();
~A();
private:
struct B;
std::unique_ptr<B> b;
};
// a.cpp
struct A::B {
// ...
};
A::A()
: b{std::make_unique<B>()} {}
A::~A() = default;
这到底是怎么回事
我们正在构建一个std::unique_ptr
来初始化b
b
是一个局部变量,这意味着它的析构函数将在作用域的末尾被调用
当实例化std::unique_ptr
的析构函数时,B
必须是完整类型
B
是不完整的类型,因此我们无法销毁B
好的,如果B
是不完整的类型,那么您不能传递std::unique_ptr
。这个限制是有意义的。pimpl表示“指向实现的指针”。外部代码访问A
的实现没有意义,因此A::b
应该是私有的。如果必须访问A::b
,则这不是pimpl,这是其他内容
如果您确实必须访问A::b
,同时隐藏b
的定义,那么有一些解决方法
std::shared_ptr
。这会以多态方式删除对象,这样在实例化std::shared_ptr
的析构函数时,B
不需要是完整类型。它没有std::unique_ptr
那么快,我个人更喜欢避免std::shared_ptr
,除非绝对必要
std::unique_ptr
。类似于std::shared_ptr
删除对象的方式。函数指针传递给负责删除的构造。这会产生不必要地携带函数指针的开销
std::unique_ptr
。这是最快的解决方案。但是,如果您有多个pimpl(但不是真正的pimpl)类,这可能会有点烦人,因为您无法定义模板。您可以这样做:
// a.hpp
struct DeleteB {
void operator()(B *) const noexcept;
};
// a.cpp
void DeleteB::operator()(B *b) const noexcept {
delete b;
}
定义自定义删除程序可能是最好的选择,但如果我是你,我会找到一种方法来避免从类外访问实现细节。你在factory.hpp中声明了create
,然后定义了create
(作为非内联函数)在foo.hpp中。发生了什么?@Kerndog73抱歉,这是一个打字错误。经过编辑。应该是一个cpp文件。谢谢你的回答。我同意你关于避免shared\u ptr
和不必要的函数指针的意见。所以总结一下,如果我不想包含类原型,我必须提供一个自定义的删除器。最好的方法是使用functor喜欢你的DeleteB
。还有一个后续问题,函子的情况不会产生任何额外的空间,对吗?@TerryTsao是的。就是这样。另一个后续问题,对于函子的情况,与默认的DeleteB情况相比,它不会产生任何额外的空间或时间,我想得对吗?@TerryTsao是的。DeleteB
require与std::default\u delete
相比,没有额外的空间或时间。事实上,std::default\u delete
的定义非常相似。
#include "A.hpp"
int main() {
A a; // this is fine since we are doing the Pimpl correctly.
// Now, I can't do this.
auto b = std::move(a.b); // <--- Can't do this.
return 0;
}
struct Foo;
std::unique_ptr<Foo> create();
#include "foo.hpp"
std::unique_ptr<Foo> create();
// a.hpp
struct A {
A();
~A();
private:
struct B;
std::unique_ptr<B> b;
};
// a.cpp
struct A::B {
// ...
};
A::A()
: b{std::make_unique<B>()} {}
A::~A() = default;
int main() {
A a;
auto b = std::move(a.b);
}
// a.hpp
struct DeleteB {
void operator()(B *) const noexcept;
};
// a.cpp
void DeleteB::operator()(B *b) const noexcept {
delete b;
}