struct CountingDeleter { void operator()(std::string *p) { ++cntr_; delete p; } unsigned long cntr_ = 0; }; int main() { CountingDeleter d1{},d2{}; { std::unique_ptr<std::string,CountingDeleter&> p1(new std::string{"first"},d1),p2(new std::string{"second"},d2); p1 = std::move(p2); // does d1 = d2 under cover } std::cout << "d1 " << d1.cntr_ << "\n"; // output: d1 1 std::cout << "d2 " << d2.cntr_ << "\n"; // output: d2 0 }
对我来说,令人吃惊的是,上述代码中的赋值对d2进行了复制.我仔细检查一下,发现这个行为是在[unique.ptr.single.asgn]标准中描述的:
(1) – Requires: If
D
is not a reference type,D
shall satisfy the requirements ofMoveAssignable
and assignment of the deleter from an rvalue of typeD
shall not throw an exception.
Otherwise,D
is a reference type;remove_reference_t<D>
shall satisfy theCopyAssignable
requirements and assignment of the deleter from an lvalue of typeD
shall not throw an exception.(2) – Effects: Transfers ownership from
u
to*this
as if by callingreset(u.release())
followed byget_deleter() = std::forward<D>(u.get_deleter())
.
为了获得我预期的行为(一个浅拷贝的删除引用),我不得不将删除引用包装到std :: reference_wrapper中:
std::unique_ptr<std::string,std::reference_wrapper<CountingDeleter>> p1(new std::string{"first"},d2); p1 = std::move(p2); // p1 now stores reference to d2 => no side effects!
对于我来说,目前在独特的ptr中处理一个删除引用是反直觉甚至容易出错的:
>当您使用引用而不是按值存储删除器时,这主要是因为您希望共享删除程序具有一些重要的独特状态.所以你不要指望共享删除器被覆盖,并且在一个唯一的ptr赋值之后它的状态丢失.
>预期unique_ptr的分配是非常重要的,特别是如果删除者是参考.但是,除此之外,您可以复制删除器可能(意外地)昂贵的东西.
>分配后,指针变得绑定到原始的删除者的副本,而不是原始的删除器本身.如果去除者的身份重要,这可能会导致一些意外的副作用.
>此外,当前的行为阻止对删除器使用const引用,因为您不能复制到const对象中.
所以我的问题是如下(它看起来像是两个问题,对不起):
>为什么标准的unique_ptr行为是这样的?
>有没有人有一个很好的例子,在unique_ptr而不是非引用类型(即值类型)中引用引用类型删除是有用的?
解决方法
如果你有状态的删除者,可能状态是重要的,并且与将被用于删除的指针相关联.这意味着当指针的所有权转移时应该转移删除状态.
但是,如果通过引用存储删除器,则意味着您关心删除者的身份,而不仅仅是其值(即它的状态),并且更新unique_ptr不应将引用重新绑定到另一个对象.
所以如果你不想要这个,你为什么甚至通过参考存储一个删除器?
参考文献的浅层副本甚至意味着什么? C中没有这样的东西.如果您不希望引用语义,请不要使用引用.
如果你真的想这样做,那么解决方案很简单:为你的删除器定义分配,不要改变计数器:
CountingDeleter& operator=(const CountingDeleter&) noexcept { return *this; }
或者,因为你真正关心的是计数器,而不是去除器,将计数器保留在除去器外,不要使用参考去除器:
struct CountingDeleter { void operator()(std::string *p) { ++*cntr_; delete p; } unsigned long* cntr_; }; unsigned long c1 = 0,c2 = 0; CountingDeleter d1{&c1},d2{&c2}; { std::unique_ptr<std::string,CountingDeleter> p1(new std::string{"first"},d2);