C++ 使用智能指针跟踪可能被删除的数据成员

C++ 使用智能指针跟踪可能被删除的数据成员,c++,smart-pointers,datamember,C++,Smart Pointers,Datamember,我有两个班A和B。我从a中确定地计算aB。对于每个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);

我有两个班
A
B
。我从
a
中确定地计算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()返回std::shared_ptr,并使my_B成为std::weake_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;
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 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;
};
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;
};
std::shared_ptr ComputeB(){
如果(我的B.expired()){
自动结果=标准::使_共享(*此);
我的B=结果;
返回结果;
}否则{
返回std::shared_ptr(my_B);
}
}
私人:
标准:弱ptr我的B;
其思想是,ComputeB的任何调用方都将成为B实例的部分所有者,这意味着只有当所有共享的PTR都被销毁时,才会销毁该实例。弱\u ptr的目的是指向B实例而不拥有它,因此使用共享\u ptr和弱\u ptr,生存期根本不与A实例绑定 为了使
B
对象在
A
中一直处于活动状态,您应该在
A
中有一个
std::weak_ptr
类型的数据成员,这将允许访问创建的
B
对象,只要该对象处于活动状态

computeB
的返回值将是
std::shared_ptr
,该值将从
std::弱_ptr
成员获取,或者如果后者持有
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
的决定应是线程安全的。为此,您应尝试使用
lock()
方法获取
weak_ptr
持有的实际
B
,然后仅当返回值为
nullptr
时,才创建一个新值


代码如下所示:

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;
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 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;
};
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;
};

保持恒定 在上面的代码中,对
ComputeB()
的调用不能在
const a
对象上完成。如果我们想支持这个功能,我们需要一个常量版本的函数。就语义而言,我更喜欢将此方法(常量和非常量版本)重命名为
getB

要显示建议的代码,该代码添加了对
常量a
对象调用
getB
的选项,我们还需要显示类
B
的示例,该类可以保存对
a
的常量或非常量引用。代码将如下所示:

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;
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 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;
};
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;
};
关于使用
union
管理同一指针的常量和非常量版本,请参见:

工作示例:


私有创建令牌 上面的代码允许任何人创建
B
的对象,这可能会导致不期望的可能性,例如通过获取
const a&a
的构造函数创建非常量
B
对象,从而在调用
getOwner()
时可能从常量转换为非常量

一个好的解决方案可能是阻止
B
的创建,并且只允许从类
A
创建它。由于创建是通过
make_shared
B
的构造函数放入
B
private
部分来完成的,因此
a
friend
声明对
a
没有帮助,调用
new B
的不是
a
。因此,我们采用私有令牌方法,如下代码所示:

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;
};

代码:

您对智能指针做了哪些研究,您不清楚智能指针的哪一部分?@t.niese最后一行是我的问题。如果有人能用聪明的指针回答这个问题,那么我的问题就得到了回答。尽管我也试图让这个问题向非智能指针解决方案开放。“当B被破坏时,什么会导致
my_B
引用
nullptr
(或
whattype
)的等价物?”这是有意义的。对我来说,
弱ptr
的工作原理与它的工作原理之间的内在区别是很奇怪的。如果
ComputeB
返回一个
B
(存储在堆栈上),这样当
B
离开作用域时被删除时,
weak\u ptr
将过期?不,weak\u ptr专门用于共享的\u ptr,因为它可以使用shared_ptr的内部引用计数来查看对象是否被破坏,但在我看来,您不能在这里返回常规的B实例,因为如果您这样做了,您将不得不在每次调用函数时创建一个新实例,并且每次都会用指向最新实例的指针覆盖my_B。请注意,
expired
的使用会导致代码不是线程安全的。可能发生的情况是,
my_B
在通过检查后立即过期-因此ComputeB将返回null ptr。最好使用
my_B.lock()
如果它是null create,则返回它。@AmirKirsh“最好使用
my_B.lock()
如果它是null create,则返回它。”如果您能澄清的话,您在这里的措辞有点混乱。