存储同一指针的多个shared_ptr
Multiple shared_ptr storing same pointer
考虑这个程序:
#include <memory>
#include <iostream>
class X
: public std::enable_shared_from_this<X>
{
public:
struct Cleanup1 { void operator()(X*) const; };
struct Cleanup2 { void operator()(X*) const; };
std::shared_ptr<X> lock1();
std::shared_ptr<X> lock2();
};
std::shared_ptr<X> X::lock1()
{
std::cout << "Resource 1 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup1());
}
std::shared_ptr<X> X::lock2()
{
std::cout << "Resource 2 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup2());
}
void X::Cleanup1::operator()(X*) const
{
std::cout << "Resource 1 unlocked" << std::endl;
}
void X::Cleanup2::operator()(X*) const
{
std::cout << "Resource 2 unlocked" << std::endl;
}
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
{
std::shared_ptr<X> p2 = x.lock2();
}
}
我在 C++11 标准第 20.7.2 节中没有看到任何内容表明任何内容都是无效的。 让两个shared_ptr
对象存储相同的指针&x
但不共享所有权,并使用不会结束*get()
生命周期的"删除器",但没有什么禁止它,这有点不寻常。 (如果其中任何一个是完全无意的,则很难解释为什么某些shared_ptr
成员函数接受std::nullptr_t
值。 正如预期的那样,程序输出:
Resource 1 locked
Resource 2 locked
Resource 2 unlocked
Resource 1 unlocked
但是现在如果我在main()
中添加一点:
int main()
{
std::cout << std::boolalpha;
X x;
std::shared_ptr<X> p1 = x.lock1();
bool test1( x.shared_from_this() );
std::cout << "x.shared_from_this() not empty: " << test1 << std::endl;
{
std::shared_ptr<X> p2 = x.lock2();
}
try {
bool test2( x.shared_from_this() );
std::cout << "x.shared_from_this() not empty: " << test2 << std::endl;
} catch (std::exception& e) {
std::cout << "caught: " << e.what() << std::endl;
}
}
然后事情变得更加棘手。 使用 g++ 4.6.3,我得到输出:
Resource 1 locked
x.shared_from_this() not empty: true
Resource 2 locked
Resource 2 unlocked
caught: std::bad_weak_ptr
Resource 1 unlocked
为什么第二次调用shared_from_this()
会失败? 满足 20.7.2.4p7 的所有要求:
要求:
enable_shared_from_this<T>
应是可访问的T
基类。*this
应该是类型T
t
对象的子对象。 至少应有一个shared_ptr
实例p
拥有&t
。
[T
是X
,t
是x
,p
是p1
。
但是 g++ 的enable_shared_from_this
基本上遵循了 20.7.2.4p10 中(非规范的)"Note"的建议实现,在类 enable_shared_from_this
中使用私有weak_ptr
成员。 如果不在enable_shared_from_this
中做一些更复杂的事情,似乎不可能解释这类问题。
这是标准的缺陷吗? (如果是这样,这里不需要评论解决方案"应该"是什么:添加一个要求,以便示例程序调用未定义的行为,更改注释以不建议这样简单的实现就足够了,....)
是的,C++11 中存在缺陷。在允许这样做时:
让两个shared_ptr对象存储相同的指针&x但不共享所有权,并使用不会结束*get()生命周期的"删除器",这有点不寻常,但没有什么禁止它。
这应该明确声明为未定义的行为,无论"删除者"做什么。 当然,从技术上讲,这样做可能并不违法。
但是,您是在对使用该代码的人撒谎。任何收到shared_ptr
的人的期望是他们现在拥有该对象的所有权。只要他们保留该shared_ptr
(或其副本),它指向的对象仍然存在。
您的代码并非如此。所以我想说它在语法上是正确的,但在语义上是无效的。
shared_from_this
的语言很好。这是需要改变的shared_ptr
语言。它应该声明创建两个"拥有"同一指针的单独唯一指针是未定义的行为。
我同意这是规范中的一个漏洞,因此是一个缺陷。它基本上与 http://open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#2179 相同,尽管该问题来自略有不同的(恕我直言更明显的破碎)角度。
我不确定我是否同意这是对shared_ptr
的误用,我认为用shared_ptrs这样做很好,因为与问题 2179 中的代码不同,您使用无操作删除器。我认为问题在于当您尝试将这种shared_ptr
的使用与enable_shared_from_this
相结合时。
所以我的第一个想法是通过扩展shared_from_this
的要求来修复它:
要求:
enable_shared_from_this<T>
应是可访问的T
基类。*this
应该是类型T
t
对象的子对象。 至少应有一个拥有&t
的shared_ptr
实例p
,拥有&t
的任何其他shared_ptr
实例应与p
共享所有权。
但这还不够,因为您的示例满足了该要求:在第二次调用 shared_from_this()
时,只有一个所有者 ( p1
),但您已经通过调用 lock2()
"破坏"了enable_shared_from_this
基类的状态。
该程序的较小形式是:
#include <memory>
using namespace std;
int main()
{
struct X : public enable_shared_from_this<X> { };
auto xraw = new X;
shared_ptr<X> xp1(xraw); // #1
{
shared_ptr<X> xp2(xraw, [](void*) { }); // #2
}
xraw->shared_from_this(); // #3
}
libstdc++,libc++和VC++(Dinkumware)的所有三个行为都相同,并在#3处抛出bad_weak_ptr
,因为在#2时,它们更新了基类的weak_ptr<X>
成员,使其与xp2
共享所有权,这超出了范围,使weak_ptr<X>
处于过期状态。
有趣的是,boost::shared_ptr
不会抛出,而是 #2 是无操作,#3 返回与xp1
共享所有权的shared_ptr
。这是为了响应错误报告,其示例与上述示例几乎完全相同。
这个问题和其他相关问题在第 17 C++中得到了澄清。 现在,std::enable_shared_from_this<T>
被指定为具有单个std::weak_ptr<T> weak_this;
成员一样。 对于 std::shared_ptr
的非数组专用化,该成员由构造函数、std::make_shared
和 std::allocate_shared
std::shared_ptr
赋值,如 [util.smartptr.shared.const]/1 中所述:
启用带有
p
shared_from_this
,对于类型为Y*
的指针p
,意味着如果Y
有一个明确且可访问的基类,该基类是enable_shared_from_this
的专用化,则remove_cv_t<Y>*
应隐式转换为T*
并且构造函数计算语句:if (p != nullptr && p->weak_this.expired()) p->weak_this = shared_ptr<remove_cv_t<Y>>(*this, const_cast<remove_cv_t<Y>*>(p));
因此,我的 OP 中第二个main
的正确行为现在是不会抛出异常,并且两个"不为空"检查都将显示为 true。 由于在调用lock2()
时,内部weak_ptr
已经拥有,因此没有expired()
,lock2()
保持weak_ptr
不变,因此第二次调用shared_from_this()
返回与p1
共享所有权的shared_ptr
。
X x;
std::shared_ptr<X> p1 = x.lock1();
(...sniped...)
}
这样的代码打破了"拥有"智能指针"的语义:
- 它们可以被复制
- 只要保留一个副本,就可以保留拥有的对象
这种不变性是如此重要,我认为这种做法应该被代码审查所拒绝。但是您建议的变体可以满足不变量:
- 对象必须动态管理(因此,不是自动的)
- 拥有对象的任何族都具有动态管理对象的共享所有权 家庭
- 的每个成员都拥有该家庭"删除程序"的所有权
所以在这里我们有共享的拥有对象,它们是拥有对象的不同"家族"的一部分,它们不是"等价的",因为它们具有不同的:
- "删除器"对象
-
use_count()
值 - 控制块
-
owner_before
结果
但它们都防止了同一对象的破坏;这是通过在每个"删除器"对象中保留shared_ptr
的副本来完成的。
std::shared_from_this
的干净替换用于完全控制 std::weak_ptr<T>
成员的初始化。
#include <memory>
#include <iostream>
#include <cassert>
// essentially like std::shared_from_this
// unlike std::shared_from_this the initialization IS NOT implicit
// calling set_owner forces YOU to THINK about what you are doing!
template <typename T>
class my_shared_from_this
{
std::weak_ptr<T> weak;
public:
void set_owner(std::shared_ptr<T>);
std::shared_ptr<T> shared_from_this() const;
};
// shall be called exactly once
template <typename T>
void my_shared_from_this<T>::set_owner(std::shared_ptr<T> shared)
{
assert (weak.expired());
weak = shared;
}
template <typename T>
std::shared_ptr<T> my_shared_from_this<T>::shared_from_this() const
{
assert (!weak.expired());
return weak.lock();
}
class X : public my_shared_from_this<X>
{
public:
struct Cleanup1 {
std::shared_ptr<X> own;
Cleanup1 (std::shared_ptr<X> own) : own(own) {}
void operator()(X*) const;
};
struct Cleanup2 {
std::shared_ptr<X> own;
Cleanup2 (std::shared_ptr<X> own) : own(own) {}
void operator()(X*) const;
};
std::shared_ptr<X> lock1();
std::shared_ptr<X> lock2();
X();
~X();
};
// new shared owner family with shared ownership with the other ones
std::shared_ptr<X> X::lock1()
{
std::cout << "Resource 1 locked" << std::endl;
// do NOT call set_owner here!!!
return std::shared_ptr<X>(this, Cleanup1(shared_from_this()));
}
std::shared_ptr<X> X::lock2()
{
std::cout << "Resource 2 locked" << std::endl;
return std::shared_ptr<X>(this, Cleanup2(shared_from_this()));
}
void X::Cleanup1::operator()(X*) const
{
std::cout << "Resource 1 unlocked" << std::endl;
}
void X::Cleanup2::operator()(X*) const
{
std::cout << "Resource 2 unlocked" << std::endl;
}
X::X()
{
std::cout << "X()" << std::endl;
}
X::~X()
{
std::cout << "~X()" << std::endl;
}
// exposes construction and destruction of global vars
struct GlobDest {
int id;
explicit GlobDest(int id);
~GlobDest();
};
GlobDest::GlobDest(int id)
: id(id)
{
std::cout << "construction of glob_dest #" << id << std::endl;
}
GlobDest::~GlobDest() {
std::cout << "destruction of glob_dest #" << id << std::endl;
}
GlobDest glob_dest0 {0};
std::shared_ptr<X> glob;
GlobDest glob_dest1 {1};
std::shared_ptr<X> make_shared_X()
{
std::cout << "make_shared_X" << std::endl;
std::shared_ptr<X> p = std::make_shared<X>();
p->set_owner(p);
return p;
}
int test()
{
std::cout << std::boolalpha;
std::shared_ptr<X> p = make_shared_X();
static std::shared_ptr<X> stat;
{
std::shared_ptr<X> p1 = p->lock1();
stat = p1;
{
std::shared_ptr<X> p2 = p->lock2();
glob = p2;
std::cout << "exit scope of p2" << std::endl;
}
std::cout << "exit scope of p1" << std::endl;
}
std::cout << "exit scope of p" << std::endl;
}
int main()
{
test();
std::cout << "exit main" << std::endl;
}
输出:
construction of glob_dest #0
construction of glob_dest #1
make_shared_X
X()
Resource 1 locked
Resource 2 locked
exit scope of p2
exit scope of p1
exit scope of p
exit main
Resource 1 unlocked
destruction of glob_dest #1
Resource 2 unlocked
~X()
destruction of glob_dest #0
- CLANG 编译器 说:变量"PTR"可能未初始化
- 在以唯一ptr为值的C++映射中,动态内存何时会被销毁
- 将 ptr 传递给 ptr 到 A 作为参数传递给 A 的函数是不好的做法吗?
- 为共享 ptr 向量实现复制 c'tor?
- 字符和整数中 **(ptr+1) 的值差异
- C++:在不中断共享的情况下通过引用传递共享 PTR?
- 如何将派生类从基 ptr 分配给 nlohmann::json
- 引用 std::shared:ptr 以避免引用计数
- 为什么我不能在不进行任何转换的情况下将浮点数放入任何类型的 ptr 中?
- 在调用函数时,ptr** 和 ptr*& 之间是否有区别,或者首选C++?
- 另一种类型的智能ptr,比如具有弱refs的unique_ptr
- 尝试打印出 *ptr++ 的值,以了解它是如何工作的
- 如何控制共享 ptr 引用计数?
- dopen():不以 root 身份运行时"failed to map segment from shared object"
- C++中的指针否定 (!ptr == NULL)
- 从const ptr*转换为ptr*时出现问题
- 无法使用 libtool 将 -shared 参数传递给 g++
- boost::shared_ptr和std::shared-ptr的同居
- 我可以用std::shared_ptr而不是boost::shared-ptr构建boost库吗
- shared-ptr-C++shared_ptr与unique_ptr用于资源管理