最近了解到智能ptrs,我正在尝试编写一个返回unique_ptrs的工厂函数。阅读了几篇关于将创建时间和明确定义的ctor和dtor放在同一个cpp文件中的文章,我想我可以这样做:
// factory.hpp
struct Foo;
std::unique_ptr<Foo> create();
// foo.cpp
struct Foo {
Foo();
~Foo();
Foo(const Foo &);
Foo(Foo &&);
};
std::unique_ptr<Foo> create() {
return make_unique<Foo>();
}
#include "factory.hpp"
int main() {
auto r = create();
return 0;
}
但是我得到了不完整的类型错误。经过几个小时的网络搜索和实验,我意识到我甚至不能这样做:
这是经典的unique_ptr Pimpl成语。
// A.hpp
struct B;
struct A {
A();
~A();
unique_ptr<B> b;
};
// A.cpp
struct B {};
A::A() = default;
A::~A() = default;
#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;
}
为了便于讨论,请忽略std::move
系列没有任何过度感觉这一事实。我得到了相同的不完整类型错误。
以上两种情况基本相同。经过一些搜索,我想我理解错误背后的原因,但我想要一些指示(双关语)和你们的确认。
明确定义create和destroy函数应该可以解决问题。但对我来说,它很难看。首先,默认删除器将在我的情况下。另一方面,在我看来,我不能使用lambda作为驱逐舰,因为lambda的类型只有编译器知道,我不能用decltype
做我的工厂函数声明。
所以我的问题是:
如果我说的话有问题,请纠正我。任何指针将不胜感激。
当编译器实例化std::unique_ptr<Foo>
的析构函数时,编译器必须找到Foo::~Foo()
并调用它。这意味着Foo
必须是std::unique_ptr<Foo>
被销毁点的完整类型。
这段代码很好:
struct Foo;
std::unique_ptr<Foo> create();
...只要你不需要调用std::unique_ptr<Foo>
的析构函数!对于将std::unique_ptr
返回给类的工厂函数,该类必须是完整类型。这就是你宣布工厂的方式:
#include "foo.hpp"
std::unique_ptr<Foo> create();
你似乎正在用std::unique_ptr
正确实现pimpl。您必须在A::~A()
完成的位置(在cpp文件中)定义B
。你必须在同一个地方定义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;
现在让我们考虑一下(我们假装我没有让b
私有):
int main() {
A a;
auto b = std::move(a.b);
}
到底发生了什么?
std::unique_ptr<B>
来初始化b
。b
是一个局部变量,这意味着它的析构函数将在作用域的末尾被调用。B
的析构函数被实例化时,std::unique_ptr<B>
必须是一个完整的类型。B
是一个不完整的类型,所以我们不能破坏b
。好的,所以如果std::unique_ptr<B>
是一个不完整的类型,你不能传递B
。这种限制是有道理的。 pimpl的意思是“指向实现的指针”。外部代码访问A
的实现是没有意义的,所以A::b
应该是私有的。如果你必须访问A::b
然后这不是pimpl,这是另外的。
如果你真的必须访问A::b
同时保持B
的定义隐藏,那么有一些解决方法。
std::shared_ptr<B>
。这会以多态方式删除对象,以便在实例化B
的析构函数时std::shared_ptr<B>
不需要是完整类型。它不像std::unique_ptr<B>
那么快,除非绝对必要,否则我个人更愿意避免使用std::shared_ptr
。
std::unique_ptr<B, void(*)(B *)>
。类似于std::shared_ptr<B>
删除对象的方式。在构造上传递函数指针,负责删除。这具有不必要地携带函数指针的开销。
std::unique_ptr<B, DeleteB>
。最快的解决方案。但是,如果你有一些pimpl(但不是真正的pimpl)类,因为你无法定义模板,这可能有点烦人。这是你怎么做的:
// a.hpp
struct DeleteB {
void operator()(B *) const noexcept;
};
// a.cpp
void DeleteB::operator()(B *b) const noexcept {
delete b;
}
定义自定义删除器可能是最好的选择,但如果我是你,我会找到一种方法来避免需要从类外部访问实现细节。