考虑这个程序:
#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中(非规范)“注意”的建议实现,使用类weak_ptr
中的私有enable_shared_from_this
成员。如果不在enable_shared_from_this
中做一些相当复杂的事情,似乎不可能解决这类问题。
这是标准中的缺陷吗? (如果是这样,这里不需要注释解决方案“应该”是什么:添加一个需求,以便示例程序调用未定义的行为,将注释更改为不建议这样一个简单的实现就足够了,....)
答案 0 :(得分:5)
是的,C ++ 11中存在缺陷。允许这样做:
让两个shared_ptr对象存储相同的指针&amp; x但不共享所有权,并使用不会终止* get()生命周期的“删除者”,这有点不寻常,但没有任何条件禁止它。
这应明确声明为未定义的行为,无论是什么“删除者”所做的。当然,以某种方式做事在技术上可能并非违法。
但是,您撒谎给使用该代码的人。收到shared_ptr
的任何人的期望是他们现在拥有该对象的所有权。只要他们保留shared_ptr
(或其副本),它指向的对象仍然存在。
您的代码不是这种情况。所以我会说它在语法上是正确的但在语义上是无效的。
shared_from_this
的语言很好。这是shared_ptr
需要改变的语言。它应该声明它是未定义的行为来创建两个独立的指针“拥有”相同的指针。
答案 1 :(得分:0)
这个问题和其他相关问题在C ++ 17中得到澄清。现在std::enable_shared_from_this<T>
被指定为拥有一个std::weak_ptr<T> weak_this;
成员。对于std::shared_ptr
的非数组专精,该成员由std::shared_ptr
构造函数std::make_shared
和std::allocate_shared
分配,如[util.smartptr.shared.const] /中所述1:
对
shared_from_this
启用p
,对于p
类型的指针Y*
,意味着如果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
不变,因此第二次调用{ {1}}返回与shared_from_this()
共享所有权的shared_ptr
。
答案 2 :(得分:0)
X x;
std::shared_ptr<X> p1 = x.lock1();
(...sniped...)
}
这样的代码打破了&#34;拥有&#34;的语义。 &#34;智能指针&#34;:
这种不变性非常重要,我认为应该通过代码审查来拒绝这种做法。但是你有一种变体可以满足不变量:
所以在这里我们共享拥有对象,这些对象属于不同的&#34;家庭&#34;拥有物品,它们不等同于#34;因为他们有不同:
use_count()
值owner_before
结果但它们都可以防止同一物体的破坏;这是通过在每个&#34;删除&#34;中保留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