使用智能指针跟踪可能被删除的数据成员
Using smart pointers to keep track of data members that may be deleted
我有两个 类 A
和 B
。我根据 A
确定性地计算 B
。对于每个 A
,我想用 my_B
跟踪 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
的等价物)?
您可以从 ComputeB() return 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 实例
使用 shared_ptr 和 weak_ptr
为了让您的 B
对象在 A
中保持活动状态,只要它仍在使用中,您应该在 A
中有一个类型为 [=19= 的数据成员] 这将允许访问创建的 B
对象,只要它还活着。
来自 computeB
的 return 值将是 std::shared_ptr<B>
,它可以从 std::weak_ptr<B>
成员获取或创建,如果后者持有 nullptr
.
线程安全
是否创建或获取现有B
的决定应为thread-safe。为此,您应尝试使用 lock()
方法获取 weak_ptr
持有的实际 B
,然后仅当 return 值为 nullptr
时创建一个新的。
代码如下所示:
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;
};
复制和赋值
上述 class 在复制和赋值中的行为是有问题的,因为默认复制构造函数和默认赋值运算符将执行 member-wise copy/assignment,这可能会导致两个不同的 A
持有一个weak_ptr
到同一个B
。这很可能不是您想要的,尤其是如果 A
是可变的(即可以更改其内部值)。
为了展示复制和赋值的建议代码,我们假设 A
拥有一个 int 成员。代码将如下所示:
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;
};
保持常量
在上面的代码中,无法对 const A
对象调用 ComputeB()
。如果我们想要支持,我们需要有这个函数的 const 版本。对于语义问题,我更喜欢将此方法(const 和 non-const 版本重命名为 getB
.
为了展示在 const A
对象上添加调用 getB
选项的建议代码,我们还需要展示一个 class B
的示例,它能够保持 const 或 non-const 对 A
的引用。代码将如下所示:
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;
};
对于 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;
}
};
关于使用union
管理同一个指针的const和non-const版本,见:
- Union of const/non-const Object Pointers
- Is const-casting via a union undefined behaviour?
工作示例:http://coliru.stacked-crooked.com/a/f696dfcf85890977
私人创建令牌
上面的代码允许任何人创建 B
的对象,这可能会导致意外的可能性,例如通过获取 const A& a
的构造函数创建 non-const B
对象,导致在调用 getOwner()
.
时可能从 const 转换为 non-const
一个好的解决方案可能是阻止 B
的创建并仅允许它来自 class A
。由于创建是通过 make_shared
将 B
的构造函数放在 B
的 private
部分并为 A
声明 friend
来完成的没用,调用 new B
的不是 A
,而是 make_shared
。因此,我们采用如下代码所示的私有令牌方法:
class A {
int i;
// only authorized entities can create B
class B_PrivateCreationToken {};
friend class B;
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),
B_PrivateCreationToken{} );
a.my_B = shared_b;
}
return shared_b;
}
public:
// public part as in above version...
private:
mutable std::weak_ptr<B> my_B;
};
对于 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, A::B_PrivateCreationToken): owner(a) {}
B(const A& a, A::B_PrivateCreationToken): owner(a) {}
// getOwner methods as in above version...
};
我有两个 类 A
和 B
。我根据 A
确定性地计算 B
。对于每个 A
,我想用 my_B
跟踪 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
的等价物)?
您可以从 ComputeB() return 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 实例
使用 shared_ptr 和 weak_ptr
为了让您的 B
对象在 A
中保持活动状态,只要它仍在使用中,您应该在 A
中有一个类型为 [=19= 的数据成员] 这将允许访问创建的 B
对象,只要它还活着。
来自 computeB
的 return 值将是 std::shared_ptr<B>
,它可以从 std::weak_ptr<B>
成员获取或创建,如果后者持有 nullptr
.
线程安全
是否创建或获取现有B
的决定应为thread-safe。为此,您应尝试使用 lock()
方法获取 weak_ptr
持有的实际 B
,然后仅当 return 值为 nullptr
时创建一个新的。
代码如下所示:
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;
};
复制和赋值
上述 class 在复制和赋值中的行为是有问题的,因为默认复制构造函数和默认赋值运算符将执行 member-wise copy/assignment,这可能会导致两个不同的 A
持有一个weak_ptr
到同一个B
。这很可能不是您想要的,尤其是如果 A
是可变的(即可以更改其内部值)。
为了展示复制和赋值的建议代码,我们假设 A
拥有一个 int 成员。代码将如下所示:
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;
};
保持常量
在上面的代码中,无法对 const A
对象调用 ComputeB()
。如果我们想要支持,我们需要有这个函数的 const 版本。对于语义问题,我更喜欢将此方法(const 和 non-const 版本重命名为 getB
.
为了展示在 const A
对象上添加调用 getB
选项的建议代码,我们还需要展示一个 class B
的示例,它能够保持 const 或 non-const 对 A
的引用。代码将如下所示:
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;
};
对于 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;
}
};
关于使用union
管理同一个指针的const和non-const版本,见:
- Union of const/non-const Object Pointers
- Is const-casting via a union undefined behaviour?
工作示例:http://coliru.stacked-crooked.com/a/f696dfcf85890977
私人创建令牌
上面的代码允许任何人创建 B
的对象,这可能会导致意外的可能性,例如通过获取 const A& a
的构造函数创建 non-const B
对象,导致在调用 getOwner()
.
一个好的解决方案可能是阻止 B
的创建并仅允许它来自 class A
。由于创建是通过 make_shared
将 B
的构造函数放在 B
的 private
部分并为 A
声明 friend
来完成的没用,调用 new B
的不是 A
,而是 make_shared
。因此,我们采用如下代码所示的私有令牌方法:
class A {
int i;
// only authorized entities can create B
class B_PrivateCreationToken {};
friend class B;
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),
B_PrivateCreationToken{} );
a.my_B = shared_b;
}
return shared_b;
}
public:
// public part as in above version...
private:
mutable std::weak_ptr<B> my_B;
};
对于 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, A::B_PrivateCreationToken): owner(a) {}
B(const A& a, A::B_PrivateCreationToken): owner(a) {}
// getOwner methods as in above version...
};