取得自己的 shared pointer

std::enable_shared_from_this

·

2 min read

目標

在 class 的 member function 裡面取得自己的 shared_ptr 或(weak_ptr)。

Example: 把底下的 code 轉成用智慧指標來管理記憶體的版本

struct Node;
struct Node {
    Node() = default;
    ~Node() = default;

    void setLeft(Node* l) {
        // assume node has no parent
        left = l;
        left->parent = this;
    }

    void setRight(Node* r) {
        right = r;
        right->parent = this;
    }

    Node* parent;
    Node* left;
    Node* right;
};

錯誤嘗試?

很遺憾,std::make_shared<Node>(this) 是完全行不通的,他會製造出一個新的 shared_ptr ,也就是說用同一個物件會被 destruct 一次以上。

https://godbolt.org/z/a56q8ajqc

#include <memory>
#include <iostream>

struct Foo {
    Foo() = default;
    ~Foo() {
        std::cout << "~Foo()" << std::endl;
    }

    auto get() {
        return std::shared_ptr<Foo>(this);
    }
};

int main() {
    auto a = std::make_shared<Foo>();
    auto b = a->get();

    return 0;
}

Result:

Program returned: 139
double free or corruption (out)
Program terminated with signal: SIGSEGV
~Foo()

std::enable_shared_from_this

https://en.cppreference.com/w/cpp/memory/enable_shared_from_this

用法在文件寫得很清楚了:

  1. 讓 class 繼承 std::enable_shared_from_this

  2. 使用 shared_from_this 或 weak_from_this (C++17)

實作

A common implementation for enable_shared_from_this is to hold a weak reference (such as std::weak_ptr) to *this. For the purpose of exposition, the weak reference is called weak-this and considered as a mutable std::weak_ptr member.

The constructors of std::shared_ptr detect the presence of an unambiguous and accessible (i.e. public inheritance is mandatory) enable_shared_from_this base and assign the newly created std::shared_ptr to weak-this if not already owned by a live std::shared_ptr.

也就是說,std::shared_ptr 在建構時,會檢查 instance 是不是繼承自 enable_shared_from_this ,是的話就把自己塞到 enable_shared_from_this 內部的 weak_ptr 。

問題:假如 instance 不是 shared_ptr 形式會怎麼樣?

https://godbolt.org/z/jjfsh49MW

#include <memory>

struct Foo : public std::enable_shared_from_this<Foo> {
    auto get() {
        return shared_from_this();
    }
};

int main() {
    Foo a;
    auto b = a.get();
    return 0;
}

結果噴出:

Program returned: 139
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
Program terminated with signal: SIGSEGV

從實作上就可以得知,因為 enable_shared_from_this 內部存的 weak_ptr 裡面是空的,然後在 shared_from_this 時嘗試將它轉成 shared_ptr,所以失敗。

所以需要避免使用者直接建立一個 instance 例如:把 constructor 設為 private,並且提供一個 static function 負責 create shared_ptr。

Code?

#include <memory>
#include <iostream>

class Node;
using NodePtr = std::shared_ptr<Node>;
using WeakNodePtr = std::weak_ptr<Node>;

class Node : public std::enable_shared_from_this<Node> {
public:
    static NodePtr create() {
        return NodePtr(new Node());
    };

    ~Node() {
        std::cout << "~Node()" << std::endl;
    }

    NodePtr getParent() const {
        return parent_.lock();
    }

    void setLeft(NodePtr node) {
        // assume node has no parent
        left_ = node;
        left_->parent_ = weak_from_this();
    }

    void setRight(NodePtr node) {
        right_ = node;
        right_->parent_ = weak_from_this();
    }

private:
    Node() = default;

    WeakNodePtr parent_;
    NodePtr left_;
    NodePtr right_;
};

int main() {
    auto root = Node::create();
    auto node1 = Node::create();
    auto node2 = Node::create();
    root->setLeft(node1);
    root->setRight(node2);
    return 0;
}