c++ - unique_ptr 和前向声明 : the proper way to code a factory function

标签 c++ c++11 smart-pointers forward-declaration

最近学习了smart 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 行毫无意义的事实。 我遇到了同样的不完整类型错误。

以上两种情况本质上是一样的。经过一番搜索,我想我明白了错误背后的原因, 但我需要你们的一些指示(双关语)和确认。

  1. 无法删除不完整的类型。这就是为什么禁止使用默认删除器创建具有不完整类型的 unique_ptrs。
  2. 如果我使用自定义删除器,我应该能够做到这一点。
  3. 我猜因为我在我的案例中使用的是默认删除器,所以我无法完成某些我不太确定的原因。

明确定义创建和销毁函数应该可以解决问题。但对我来说,这很丑陋。其一,默认删除器就可以解决我的问题。 另一方面,在我看来我不能将 lambda 用于驱逐舰,因为 lambda 的类型只有编译器知道, 而且我无法使用 decltype 进行工厂函数声明。

所以我的问题是:

  1. 失败的原因是什么?
  2. 编写返回 unique_ptr 的工厂函数的正确方法是什么?

如果我说的不对请指正。任何指针将不胜感激。

最佳答案

当编译器实例化std::unique_ptr<Foo>的析构函数时, 编译器必须找到 Foo::~Foo()并调用它。这意味着 Foostd::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()B 的位置已完成(在 cpp 文件中)。您必须定义 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 设为私有(private)):

int main() {
  A a;
  auto b = std::move(a.b);
}

这里到底发生了什么?

  1. 我们正在 build std::unique_ptr<B>初始化 b .
  2. b是一个局部变量,这意味着它的析构函数将在作用域的末尾被调用。
  3. Bstd::unique_ptr<B> 的析构函数时必须是完整类型被实例化。
  4. B是一个不完整的类型,所以我们不能销毁 b .

好的,所以你不能绕过 std::unique_ptr<B>如果B是一个不完整的类型。这种限制是有道理的。 pimpl 的意思是“指向实现的指针”。外部代码访问 A 的实现没有意义所以A::b应该是私有(private)的。如果您必须访问 A::b那么这不是粉刺,这是别的东西。

如果您真的必须访问 A::b同时保持 B 的定义隐藏然后有一些解决方法。

std::shared_ptr<B> .这将以多态方式删除对象,以便 Bstd::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;
}

定义一个自定义删除器可能是最好的选择,但如果我是你,我会找到一种方法来避免需要从类外部访问实现细节。

关于c++ - unique_ptr 和前向声明 : the proper way to code a factory function,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55770004/

相关文章:

c++ - 智能指针 + "this"被认为是有害的?

c++ - 从无序数据构建 QVariant 列表

c++ - 如何在 `push_*()` 和 `emplace_*()` 函数之间进行选择?

c++ - 使用 '&'进行迭代时 'auto'符号有什么作用

C++ 将引用变量传递给 g++-4.7.4 中的线程

c++ - 类型 ‘serial::serial’ 命名构造函数,而不是 C/C++ 中的类型

c++ - 避免由 new(new[]) 引起的内存泄漏

c++ - 带智能指针的 N​​-Ary 树设计

c++ - 反向传播神经网络无法正确学习

c++ - 在运行时使用 C++ 使用转义序列