enable_shared_from_this 的坑

Posted on Mon 06 February 2023 in Journal

Abstract enable_shared_from_this 的坑
Authors Walter Fan
 Category    learning note  
Status v1.0
Updated 2023-02-06
License CC-BY-NC-ND 4.0

std::enable_shared_from_this 是一个 C++ 标准库中的模板类,它提供了一种机制,让一个对象能够从一个 std::shared_ptr 智能指针中获取指向自己的共享指针。

具体地说,如果一个类继承自 std::enable_shared_from_this,那么这个类的实例对象可以通过调用 shared_from_this() 成员函数,获得一个指向自己的 std::shared_ptr 智能指针。这个指针可以和其他 std::shared_ptr 共享控制块,从而安全地管理对象的生命周期。

std::enable_shared_from_this 通常用在需要在类的成员函数中保存对象的共享指针的场合。例如,在实现异步回调函数时,可能需要把对象的共享指针保存下来,以便在回调函数被调用时可以访问对象的状态。

需要注意的是,std::enable_shared_from_this 要求对象必须被至少一个 std::shared_ptr 所拥有。如果一个对象不被 std::shared_ptr 持有,或者被裸指针所持有,那么调用 shared_from_this() 会导致 undefined behavior。

另外,由于 std::enable_shared_from_this 是一个模板类,因此需要在使用之前声明类的类型,并且这个类必须是可继承的。通常情况下,继承自 std::enable_shared_from_this 的类需要使用 std::shared_ptr 来进行对象的管理。

std::enable_shared_from_this is a C++11 library feature that allows an object managed by a std::shared_ptr to obtain a std::shared_ptr instance pointing to itself. This can be useful in scenarios where multiple std::shared_ptr instances are pointing to the same object and you need to avoid creating additional copies of the object.

std::enable_shared_from_this is implemented as a mixin class that can be inherited by a class that wants to use this feature. The class must have a public constructor that takes no arguments. When an object is created, it is typically wrapped in a std::shared_ptr instance. By calling the shared_from_this() member function of the object, a new std::shared_ptr instance is returned that shares ownership of the object with the original std::shared_ptr instance.

It's important to note that shared_from_this() can only be called on an object that has already been managed by a std::shared_ptr. If called on an object that is not currently managed by a std::shared_ptr, it will result in undefined behavior.

Using std::enable_shared_from_this can help improve the performance and memory usage of your code by avoiding unnecessary copies of objects. It can also be useful when working with objects that have complex dependencies or lifecycle management requirements.

#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::shared_ptr<MyClass> get_shared_ptr() {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
    std::shared_ptr<MyClass> ptr2 = ptr->get_shared_ptr();
    // 现在,ptr 和 ptr2 指向同一个对象
    return 0;
}

在上面的例子中,MyClass 继承自 std::enable_shared_from_this,并且通过 shared_from_this 函数返回一个指向当前对象的 std::shared_ptr。

在 main 函数中,我们首先使用 std::make_shared 创建了一个指向 MyClass 对象的 std::shared_ptr,然后使用 get_shared_ptr 函数获取了一个指向同一对象的 std::shared_ptr。

需要注意的是,如果一个类使用了 std::enable_shared_from_this,那么不能直接使用 std::shared_ptr 的构造函数来创建该类的对象,否则会导致 shared_from_this 函数无法正确工作。

此外,还需要注意的是,在使用 shared_from_this 函数之前,必须保证至少已经有一个指向该对象的 std::shared_ptr,否则会抛出 std::bad_weak_ptr 异常。因此,在构造函数中不应该调用 shared_from_this 函数

注: 以上文字来自 chatgpt 的输出, 解释得比我说的好

下面是我写的一个简单的例子, 埋了 3 个坑

#include "run_example.h"
#include <string>
#include <map>
#include <memory>

using namespace std;

class Animal;

class Zookeeper {
public:
    void addAnimal(std::shared_ptr<Animal> animal);
    uint32_t getAnimalCount(const std::string& name);
    const std::shared_ptr<Animal>& getAnimal(const std::string& name);
private:
    std::map<std::string, std::shared_ptr<Animal>> m_animals;
    std::map<std::string, uint32_t> m_counter;
    std::shared_ptr<Animal> m_empty_animal = nullptr;
};

class Animal: public std::enable_shared_from_this<Animal> {
public:

    Animal(const std::string name, const std::shared_ptr<Zookeeper>& keeper)
    :m_name(name), m_keeper(keeper) {
        //1) !!! dangerous, the construtor have not been done, will crash if
        //m_keeper->addAnimal(getThisEntity());
    }

    //correct
    std::shared_ptr<Animal> getThisEntity() {
        return shared_from_this();
    }
    //2) dangerous of double delete
    std::shared_ptr<Animal> getThisSharedPtr() {
        return shared_ptr<Animal>(this);
    }

    //3) dangerous of the raw pointer
    Animal* getThisPtr() {
        return this;
    }

    const std::string& getName() const {
        return this->m_name;
    }

    void snarl() {
        if (m_name == "dog") {
            std::cout << "wangwang!" << std::endl;
        } else {
            std::cout << "haha!" << std::endl;
        }

    }
private:
    std::string m_name;
    std::shared_ptr<Zookeeper> m_keeper;
};


void Zookeeper::addAnimal(std::shared_ptr<Animal> animal) {
    if(auto[iter, inserted]{ m_counter.insert({animal->getName(), 1}) }; !inserted) {
        iter->second ++;
        DEBUG_TRACE("existed animal: " << iter->second << ", name=" << animal->getName());
    } else {
        m_animals.insert({animal->getName(), animal});
        DEBUG_TRACE("insert animal: " << getAnimalCount(animal->getName()) << ", name=" << animal->getName());
    }
}

uint32_t Zookeeper::getAnimalCount(const std::string& name) {
    if (auto iter = m_counter.find(name); iter != m_counter.end()) {
        return iter->second;
    }
    return 0;
}

const std::shared_ptr<Animal>& Zookeeper::getAnimal(const std::string& name) {
    if (auto iter = m_animals.find(name); iter != m_animals.end()) {
        return iter->second;
    }
    return m_empty_animal;
}

int enable_shared_from_this_demo(int argc, char* argv[]) {
    auto keeper= make_shared<Zookeeper>();
    auto dog = make_shared<Animal>("dog", keeper);
    DEBUG_TRACE(". m_name=" << dog->getName());

    auto dog2 = dog->getThisEntity();
    DEBUG_TRACE(". m_name=" << dog2->getName());

    assert(dog == dog2);
    assert(!(dog < dog2 || dog2 < dog)); // ptr1 and ptr2 must share ownership

    keeper->addAnimal(dog);
    keeper->addAnimal(dog2);

    DEBUG_TRACE(keeper->getAnimalCount("dog"));
    auto search = keeper->getAnimal("dog");
    if (search)
        search->snarl();

    return 0;
}

后面两个坑很容易避免, 而第一个坑其实最容易忽略, 在构造函数中千万不要调用 shared_from_this() 特别是如果这个智能针传到其他类中, 会引起不必要的错误


本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。