最近学习了智能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
行没有限制的事实。
我遇到同样的不完整类型错误。
以上两种情况基本相同。经过一番搜索,我想我理解了错误的原因, 但是我想要一些指针(双关语意)和你们的确认。
明确定义创建和销毁功能应该可以解决问题。但是对我来说,这很丑。首先,在我的情况下,默认删除器将起作用。
另外,在我看来,我不能将lambda用作毁灭者,因为lambda的类型只有编译器才知道,
而且我无法使用decltype
进行工厂函数声明。
所以我的问题是:
如果我所说的话有误,请纠正我。任何指针将不胜感激。
答案 0 :(得分:2)
当编译器实例化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;
}
定义自定义删除器可能是最好的选择,但是如果我是你,我会找到一种避免需要从类外部访问实现详细信息的方法。