oli*_*ora 11 c++ smart-pointers unique-ptr c++11
当您拥有unique_ptr由引用存储的自定义删除器时,对该案例进行成像:
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
}
Run Code Online (Sandbox Code Playgroud)
这是一个惊喜,我在上面的代码分配具有复制的副作用d2成d1.我仔细检查了一下,发现这种行为与[unique.ptr.single.asgn]中的标准一样:
(1) - 要求:如果
D不是参考类型,D则应满足MoveAssignable删除者的要求和类型的左值,D不得抛出异常.否则,D是一个参考类型;remove_reference_t<D>应满足CopyAssignable要求,并从类型的左值分配删除D者不得抛出例外.(2) - 效果:将所有权转移
u到*this似乎通过呼叫reset(u.release())后跟get_deleter() = std::forward<D>(u.get_deleter()).
为了获得我期望的行为(删除器引用的浅表副本),我必须将删除器引用包装到std::reference_wrapper:
std::unique_ptr<std::string, std::reference_wrapper<CountingDeleter>>
p1(new std::string{"first"} , d1),
p2(new std::string{"second"}, d2);
p1 = std::move(p2); // p1 now stores reference to d2 => no side effects!
Run Code Online (Sandbox Code Playgroud)
对我来说,当前处理独特ptr中的删除引用是违反直觉的,甚至容易出错:
当您通过引用而不是值存储删除器时,这主要是因为您希望共享删除器具有一些重要的唯一状态.因此,您不希望共享删除器被覆盖,并且在唯一的ptr分配后其状态将丢失.
预计unique_ptr的赋值非常小,特别是如果删除器是参考.但是,不是这样,你可以复制删除器,这可能是(出乎意料)昂贵的.
赋值后,指针将绑定到原始删除器的副本,而不是原始删除器本身.如果删除者的身份很重要,这可能会导致一些意想不到的副作用.
此外,当前行为阻止使用const引用到删除器,因为您无法复制到const对象.
IMO最好禁止引用类型的删除并仅接受可移动值类型.
所以我的问题是以下(它看起来像两个问题,对不起):
标准的unique_ptr行为是否有任何理由?
有没有一个很好的例子,有一个引用类型删除器unique_ptr而不是非引用类型(即值类型)是有用的?
Jon*_*ely 13
这是一个功能.
如果您有状态删除器,则可能是状态很重要,并且与将用于删除的指针相关联.这意味着当指针的所有权转移时,应该转移删除状态.
但是如果通过引用存储删除器,则意味着您关心删除器的标识,而不仅仅是它的值(即它的状态),并且更新不unique_ptr应该将引用重新绑定到不同的对象.
所以如果你不想要这个,为什么你甚至通过引用存储删除器?
参考文献的浅层副本甚至意味着什么?在C++中没有这样的东西.如果您不想要引用语义,请不要使用引用.
如果你真的想这样做,那么解决方案很简单:为你的删除器定义赋值,不要改变计数器:
CountingDeleter&
operator=(const CountingDeleter&) noexcept
{ return *this; }
Run Code Online (Sandbox Code Playgroud)
或者,因为你真正关心的是计数器,而不是删除器,将计数器保留在删除器之外,不要使用引用删除器:
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"} , d1),
p2(new std::string{"second"}, d2);
Run Code Online (Sandbox Code Playgroud)
拥有引用数据成员通常会导致令人惊讶的结果,因为分配给引用具有非值语义,因为无法重新分配引用来引用另一个对象。基本上,引用数据成员会破坏赋值运算符的语义。
使用指针成员可以解决这个问题。或者,使用std::reference_wrapper<>和std::ref()。
为什么它执行引用存储的删除器的深复制而不是浅复制?
它执行成员明智的复制。如果要复制的值是指针,那么它恰好是浅复制。
| 归档时间: |
|
| 查看次数: |
599 次 |
| 最近记录: |