为什么共享指针赋值 'swap'?
Why shared pointer assignment does 'swap'?
据我所知,分配的共享指针应该表现得像:
a) if (--this->count == 0) { release this->count and this->obj }
b) this->count = r->count, this->obj = r->obj;
boost的作用只是
shared_ptr & operator=( shared_ptr const & r ) BOOST_NOEXCEPT
{
this_type(r).swap(*this);
return *this;
}
那么交换背后的魔力是什么?为什么它有效?
到赋值运算符作用域结束时(结束 }
),原始 sptr 对象的引用计数将减一,因为 r
不再指向它。因此,它在功能上正是您上面所描述的。
a) if (--this->count == 0) { release this->count and this->obj }
不,shared_ptr
保留两个计数,一个用于对象,一个用于包含引用计数的控制块。当第一个达到零时释放 this->obj
,当第二个达到零时释放 this->count
(和第二个计数)。
b) this->count = r->count, this->obj = r->obj;
不,您缺少引用计数增量。此外,正如 Yakk 的回答所指出的那样,您必须检查自赋值或首先增加右侧,以避免在自赋值时错误地破坏对象。
所以您的理解是 incomplete/incorrect,但是如果您按照 Boost 代码进行操作,您会发现它做的事情完全正确。它增加 r
对象的引用计数,交换所有必要的值,然后减少 *this
.
的原始值的引用计数
增加引用计数的步骤已经在复制构造函数中实现,因此它重新使用了它。
交换所有必要值的步骤已经在 swap
成员中实现,因此它使用它。
减少引用计数(并在需要时释放任何东西)的步骤已经由析构函数完成,因此它使用了那个。
这是很好的代码重用。另一种方法是重复在复制构造函数、交换成员和析构函数中找到的所有相同代码,这将是冗余且容易出错的。
这是复制交换习语。它并不总是有效,但在这种情况下是有效的。
要分配一个引用计数对象,您首先 (a) 增加右侧的引用计数,然后 (b) 减少左侧的引用计数,以及 (c) 存储 rhs 的状态在 lhs.
(b) 和 (c) 的顺序仅在可能发生异常时才重要,但 (a) 必须 在 (b) 之前发生,以防出现自赋值(或同等学历)。
copy swap idiom 这样做:
- (1) 将 rhs 复制到临时文件。
- (2) 用临时(包含 rhs 状态的副本)交换 lhs 状态
- (3) 销毁临时
这会在 (1) 中执行 (a),然后在 (2) 中执行 (c),然后在 (3) 中执行 (b)。它以一种特别安全的异常方式来实现:代码处于 'strange' 状态的唯一时间是在交换中,并且交换很容易进行异常防护。
Copy swap可以作为一个通用的好写赋值,但是没有复用lhs的内部资源,这样比较节省成本。在这种情况下这并不重要,因为 lhs 内部资源是指向共享状态的指针。
据我所知,分配的共享指针应该表现得像:
a) if (--this->count == 0) { release this->count and this->obj }
b) this->count = r->count, this->obj = r->obj;
boost的作用只是
shared_ptr & operator=( shared_ptr const & r ) BOOST_NOEXCEPT
{
this_type(r).swap(*this);
return *this;
}
那么交换背后的魔力是什么?为什么它有效?
到赋值运算符作用域结束时(结束 }
),原始 sptr 对象的引用计数将减一,因为 r
不再指向它。因此,它在功能上正是您上面所描述的。
a) if (--this->count == 0) { release this->count and this->obj }
不,shared_ptr
保留两个计数,一个用于对象,一个用于包含引用计数的控制块。当第一个达到零时释放 this->obj
,当第二个达到零时释放 this->count
(和第二个计数)。
b) this->count = r->count, this->obj = r->obj;
不,您缺少引用计数增量。此外,正如 Yakk 的回答所指出的那样,您必须检查自赋值或首先增加右侧,以避免在自赋值时错误地破坏对象。
所以您的理解是 incomplete/incorrect,但是如果您按照 Boost 代码进行操作,您会发现它做的事情完全正确。它增加 r
对象的引用计数,交换所有必要的值,然后减少 *this
.
增加引用计数的步骤已经在复制构造函数中实现,因此它重新使用了它。
交换所有必要值的步骤已经在 swap
成员中实现,因此它使用它。
减少引用计数(并在需要时释放任何东西)的步骤已经由析构函数完成,因此它使用了那个。
这是很好的代码重用。另一种方法是重复在复制构造函数、交换成员和析构函数中找到的所有相同代码,这将是冗余且容易出错的。
这是复制交换习语。它并不总是有效,但在这种情况下是有效的。
要分配一个引用计数对象,您首先 (a) 增加右侧的引用计数,然后 (b) 减少左侧的引用计数,以及 (c) 存储 rhs 的状态在 lhs.
(b) 和 (c) 的顺序仅在可能发生异常时才重要,但 (a) 必须 在 (b) 之前发生,以防出现自赋值(或同等学历)。
copy swap idiom 这样做:
- (1) 将 rhs 复制到临时文件。
- (2) 用临时(包含 rhs 状态的副本)交换 lhs 状态
- (3) 销毁临时 这会在 (1) 中执行 (a),然后在 (2) 中执行 (c),然后在 (3) 中执行 (b)。它以一种特别安全的异常方式来实现:代码处于 'strange' 状态的唯一时间是在交换中,并且交换很容易进行异常防护。
Copy swap可以作为一个通用的好写赋值,但是没有复用lhs的内部资源,这样比较节省成本。在这种情况下这并不重要,因为 lhs 内部资源是指向共享状态的指针。