问题描述
我有两个班A
和B
。我从B
确定地计算A
。对于每个A
,我想一直跟踪B
与my_B
的情况。 B
被破坏后,我希望将my_B
更改为nullptr
之类的东西。
class A{
// stuff
public:
B ComputeB(){
if (my_B is null){
B result = B(A);
my_B = B; // some kind of reference
return B(A);
}
else {
return my_B;
}
}
~A(){ /* Do I need a destructor? */ }
private:
WhatTypeHere my_B;
}
B
被销毁后,是什么导致my_B
引用nullptr
(或与WhatTypeHere
等效)?
解决方法
使用shared_ptr和weak_ptr
为了使B
对象在A
中保持活动状态,只要该对象仍在使用中,就应该在A
类型为std::weak_ptr<B>
的数据成员中使用只要创建的B
对象仍然有效,就可以访问它。
computeB
的返回值为std::shared_ptr<B>
,可以从std::weak_ptr<B>
成员那里获取,也可以创建(如果后者持有nullptr
的话)。
线程安全
是创建还是获取现有B
的决定应该是线程安全的。为此,您应尝试使用B
方法来获取weak_ptr
持有的实际lock()
,然后仅在返回值为nullptr
时创建一个新值。 / p>
代码如下:
class A {
// stuff
public:
std::shared_ptr<B> ComputeB() {
std::shared_ptr<B> shared_b = my_B.lock();
if (!shared_b){
shared_b = std::make_shared<B>(*this);
my_B = shared_b;
}
return shared_b;
}
// no need for a destructor,unless "stuff" needs one
// ~A(){}
private:
std::weak_ptr<B> my_B;
};
复制和分配
上述类在复制和赋值中的行为是有问题的,因为默认的复制构造函数和默认的赋值运算符将执行成员级的复制/赋值,这可能导致两个不同的A
持有{{ 1}}移至相同的weak_ptr
。很有可能这不是您想要的,特别是如果B
是可变的(即可以更改其内部值)的话。
要显示建议的复制和赋值代码,我们假设A
拥有一个int成员。代码如下:
A
保持常量性
在上面的代码中,无法在class A {
int i;
public:
A(int i1): i(i1) {}
void set(int i1) { i = i1; }
std::shared_ptr<B> ComputeB() {
std::shared_ptr<B> shared_b = my_B.lock();
if (!shared_b){
shared_b = std::make_shared<B>(*this);
my_B = shared_b;
}
return shared_b;
}
A(const A& a): i(a.i) {}
A& operator=(const A& a) { i = a.i; return *this; }
~A() {}
private:
std::weak_ptr<B> my_B;
};
对象上进行对ComputeB()
的调用。如果要支持,我们需要此函数的const版本。就语义而言,我更喜欢将此方法( const 和 non-const 版本)重命名为const A
。
要显示建议的代码以添加在getB
对象上调用getB
的选项,我们还需要提供一个示例const A
的示例,该示例可以容纳 const 或 non-const 对B
的引用。代码如下:
A
对于B:
class A {
int i;
// to prevent code duplication for the const and non-const versions
template<typename AType>
static auto getB(AType&& a) {
std::shared_ptr<B> shared_b = a.my_B.lock();
if (!shared_b){
shared_b = std::make_shared<B>(std::forward<AType>(a));
a.my_B = shared_b;
}
return shared_b;
}
public:
A(int i1): i(i1) {}
void set(int i1) { i = i1; }
std::shared_ptr<B> getB() {
return getB(*this);
}
std::shared_ptr<const B> getB() const {
return getB(*this);
}
A(const A& a): i(a.i) {}
A& operator=(const A& a) { i = a.i; return *this; }
~A() {}
private:
mutable std::weak_ptr<B> my_B;
};
关于使用class B {
union Owner {
A* const ptr;
const A* const const_ptr;
Owner(A& a): ptr(&a) {}
Owner(const A& a): const_ptr(&a) {}
} owner;
public:
B(A& a): owner(a) {}
B(const A& a): owner(a) {}
const A& getOwner() const {
return *owner.const_ptr;
}
A& getOwner() {
return *owner.ptr;
}
};
管理同一指针的 const 和非const 版本,请参见:
工作示例:http://coliru.stacked-crooked.com/a/f696dfcf85890977
私人创建令牌
上面的代码允许任何人创建union
的对象,这可能会导致不希望的可能性,例如通过获取B
的构造函数创建一个非const B
对象,结果是调用const A& a
时可能从const转换为非const。
一个好的解决方案可能是阻止getOwner()
的创建,并仅允许其从类B
开始。由于创建是通过A
完成的,因此将make_shared
的构造函数放在B
的{{1}}部分中,并为private
声明了B
如果没有帮助,不是friend
在呼叫A
,而是A
。因此,我们采用私有令牌方法,如以下代码所示:
new B
对于B:
make_shared
代码:http://coliru.stacked-crooked.com/a/f656a3992d666e1e
,您可以从ComputeB()返回std :: shared_ptr,并将my_B设为std :: weak_ptr。像这样:
std::shared_ptr<B> ComputeB() {
if (my_B.expired()) {
auto result = std::make_shared<B>(*this);
my_B = result;
return result;
} else {
return std::shared_ptr<B>(my_B);
}
}
private:
std::weak_ptr<B> my_B;
这个想法是,ComputeB的任何调用者都成为B实例的部分所有者,这意味着它仅在销毁所有实例的shared_ptrs时才被销毁。 weak_ptr的目的是在不拥有它的情况下指向B实例,因此生存期完全不依赖于A实例