【问题标题】:Why doesn't std::unique_ptr have an aliasing constructor like std::shared_ptr has?为什么 std::unique_ptr 没有像 std::shared_ptr 那样的别名构造函数?
【发布时间】:2016-01-08 03:11:36
【问题描述】:

我刚刚发现std::shared_ptr 的“别名构造函数”,发现自己在问“为什么 std::unique_ptr 没有对应的?

也就是说,如果你想分配一个Foo 以便你可以将它的Bar 成员传递给一个应该完全管理Foo 生命周期的函数,那么能够这样做?

#include <memory>

struct B {}
struct A {
  B b;
}

void f(std::unique_ptr<B> b);

std::unique_ptr<A> a = std::make_unique<A>();
std::unique_ptr<B> b { std::move(a), &(a->b) };  // a now invalid.
f(std::move(b));  // f now responsible for deleting the A.

这适用于 std::shared_ptr (http://ideone.com/pDK1bc)

#include <iostream>
#include <memory>
#include <string>

struct B {
  std::string s;
};
struct A {
  B b;
  A(std::string s) : b{s} {};
  ~A() { std::cout << "A deleted." << std::endl; }
};

void f(std::shared_ptr<B> b) {
  std::cout << "in f, b->s = " << b->s << " (use_count=" << b.use_count() << ")" << std::endl;
}

int main() {
  std::shared_ptr<A> a = std::make_shared<A>("hello");
  std::shared_ptr<B> b { a, &(a->b) };
  a.reset();  // a now invalid.
  std::cout << "before f, b->s = " << b->s << " (use_count=" << b.use_count() << ")" << std::endl;
  f(std::move(b));  // f now responsible for deleting the A.
  std::cout << "after f" << std::endl;
  return 0;
}

输出预期

before f, b->s = hello (use_count=1)
in f, b->s = hello (use_count=1)
A deleted.
after f

不包括这样的东西有合理的理由吗?和/或,用unique_ptr&lt;B&gt; 模拟它并使用删除A 的自定义删除器来模拟它是不是一个坏主意?

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    我认为“问题”在于,与std::shared_ptr 不同,std::unique_ptr 的删除器没有被类型擦除。 std::unique_ptr&lt;T&gt; 的默认删除器(大小为零,编码到类型本身作为几乎不可见的默认类型参数)就是 [](T * p){ delete p; }。但是很明显,通过std::make_unique&lt;B&gt; 创建的std::unique_ptr&lt;B&gt; 和通过指向A 对象的B 成员创建的A 不能具有相同的删除器。后一种情况的删除器必须做一些指针运算来获取原始的A * 指针。如果两个删除器都存储一个偏移量或指向原始对象的内部指针,那么这两个删除器只能具有相同的类型。这将不再具有零大小。与手动执行 newdelete 相比,std::unique_ptr 的设计开销为零,这是一件好事。我没有看到使用您自己的存储附加指针的删除器有任何直接的缺点,尽管我仍然必须遇到一个我觉得这很有用的用例。

    【讨论】:

      【解决方案2】:

      shared_ptr 有引用计数开销。在它的引用计数块中,它还存储了一个显式删除器(因为如果你在堆上存储,还有几个字节呢?)

      这也是为什么基类型的shared_ptr 可以记住删除没有虚拟 dtor 的派生类型。

      另一方面,unique_ptr 将其删除器存储在实例中,并且默认删除器是无状态的——使用了 0 个字节。这使得unique_ptr 在内存使用方面比原始指针零开销。

      无状态删除器无法记住删除其他内容。

      您可以向unique_ptr 添加一个支持别名的有状态删除器,但您必须手动设置别名。其中一个构造函数接受一个指针和一个删除器。

      【讨论】:

      • 然后由于删除器是类型的一部分,除非您键入删除删除器,否则它不能与普通的unique_ptr&lt;T&gt; 很好地互操作,这会增加额外的开销。
      • @T.C.:即使您键入擦除删除器,它也不能与 unique_ptr&lt;T, default_deleter&lt;T&gt;&gt; 很好地互操作。至少,只能在一个方向上进行转换。
      • @ben 理论上是双向的,只要允许失败。
      猜你喜欢
      • 2015-08-28
      • 1970-01-01
      • 2015-07-23
      • 2020-01-20
      • 2017-08-13
      • 1970-01-01
      • 2018-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多