为什么std::unique_ptr没有像std::shared_ptr一样的别名构造函数?

8
我刚刚发现了`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

为什么没有包含这样的东西有合理的逻辑原因吗?或者,使用一个自定义删除器删除Aunique_ptr<B>来模拟它是一个不好的想法吗?

2个回答

11
我认为“问题”在于,与 std::shared_ptr 不同,std::unique_ptr 的删除器没有进行类型擦除。 std::unique_ptr<T> 的默认删除器(大小为零,作为几乎看不见的默认类型参数编码进类型本身)仅为 [](T * p){ delete p; }。但很明显,通过 std::make_unique<B> 创建的 std::unique_ptr<B> 和指向 A 对象的 B 成员所创建的指针是不同的。后一种情况下的删除器需要进行指针运算才能获取原始的 A * 指针。只有当两个删除器都存储偏移量或内部指针以指向原始对象时,这两个删除器才可能具有相同的类型。而那将不再具有零大小。 std::unique_ptr 旨在与手动使用 newdelete 相比,具有零开销,这是一件好事。我没有发现使用自己的删除器会产生任何立即的缺点,尽管我仍需要遇到一个使用案例才能发现其有用之处。

7

shared_ptr有引用计数开销,在其引用计数块中也存储了一个显式的删除器(因为如果你在堆上存储,几个字节再多也无妨)。

这也是为什么shared_ptr指向基类型时可以记得删除派生类型而不需要虚析构函数的原因。

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

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

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


然后由于删除器是类型的一部分,它与普通的 unique_ptr<T> 不兼容,除非您对删除器进行类型擦除,这会增加更多的开销。 - T.C.
@T.C.:即使您使用type erase删除器,它也无法与unique_ptr<T, default_deleter<T>>很好地交互。至少,在一个方向上可以进行转换。 - Ben Voigt
@ben 理论上两种方式都可以,只要允许失败。 - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接