每日 C++ - std::enable_shared_from_this 特性

前言

毫无疑问,C++11 引入的智能指针的特性对于 C++ 开发来说友好很多,但是同样的也引入了一些开发误区。上一篇 《shared_ptr 引入的循环引用的问题》介绍了 shared_ptr 使用的注意点,今天这边作为一点补充。

std::enable_shared_from_this 使用场景

在高级编程语言如 Java、C# 开发中,我们会发现对象是引用传递的,这个特点在 C++ 中我们可以使用 shared_ptr 来处理。但是在实际开发中,我们往往需要在对象内的方法中传递自身。这样说可能有些抽象,我们来看下面一个例子

#include <iostream>
#include <string>
#include <memory>

class Person
{
public:
	Person() {
        std::cout << "Person ctor." << std::endl;
    };
	virtual ~Person()
    {
        std::cout << "Person dtor." << std::endl;
    }
	virtual const std::string getName() const = 0;
};
class ActionWear
{
public:
	ActionWear(const std::shared_ptr<Person> person) : person(person)
	{
        std::cout << "Action wear ctor, person use_count = " << person.use_count() << std::endl;
	}
    ~ActionWear()
    {
        std::cout << "Action wear dtor." << std::endl;
    }
	void action()
	{
		std::cout << "wear action" << std::endl;
	}
    std::shared_ptr<Person> getPerson()
    {
        std::cout << "Action wear get person, person use_count = " << person.use_count() << std::endl;
        return person;
    }
private:
	std::shared_ptr<Person> person;
};
class Worker : public Person
{
public:
	Worker()
	{
        std::cout << "Worker ctor." << std::endl;
		actionWear = std::make_shared<ActionWear>(std::shared_ptr<Worker>(this));
	}
	~Worker()
    {
        std::cout << "Worker dtor." << std::endl;
    }
	const std::string getName() const override
	{
		return "Worker";
	}
    std::shared_ptr<ActionWear> getActionWear()
    {
        return actionWear;
    }
protected:
	std::shared_ptr<ActionWear> actionWear;
};

int main()
{
    std::shared_ptr<Worker> worker = std::make_shared<Worker>();
    return 0;
}

我们运行后,打印如下

Person ctor.
Worker ctor.
Action wear ctor, person use_count = 1
Worker dtor.
Person dtor.

[Done] exited with code=3221226356 in 0.751 seconds

我们发现 Worker 被析构了两次,最后程序异常退出,原因就是如上打印,内存对象被 double delete 了。我们来捋下整个过程。

  1. ActionWear 构造时,将 std::shared_ptr(this) 封装,此时该 shared_ptr 引用计数是 1(这个引用计数跟之前 std::make_shared 的并没有关系)
  2. 传入 ActionWear 构造函数赋值给 ActionWear 的 person 成员,此时 shared_ptr 引用计数是 2
  3. 构造函数结束,shared_ptr 作为参数后引用计数-1,所以我们调用 ActionWear 的 getPerson 返回的 shared_ptr 其引用计数是 1

所以在 ActionWear 被析构时,其成员对象 person 会先被析构(引用计数是 1),最终导致异常 Worker 被析构两次的异常发生。

怎么处理这类问题呢?

C++ 特性 std::enable_shared_from_this 可以解决这类问题。我们先来看 std::enable_shared_from_this 官方用法

#include <memory>
#include <iostream>
 
struct Good: std::enable_shared_from_this<Good> // note: public inheritance
{
    std::shared_ptr<Good> getptr() {
        return shared_from_this();
    }
};
 
struct Bad
{
    std::shared_ptr<Bad> getptr() {
        return std::shared_ptr<Bad>(this);
    }
    ~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
 
int main()
{
    // Good: the two shared_ptr's share the same object
    std::shared_ptr<Good> gp1 = std::make_shared<Good>();
    std::shared_ptr<Good> gp2 = gp1->getptr();
    std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';
 
    // Bad: shared_from_this is called without having std::shared_ptr owning the caller 
    try {
        Good not_so_good;
        std::shared_ptr<Good> gp1 = not_so_good.getptr();
    } catch(std::bad_weak_ptr& e) {
        // undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)
        std::cout << e.what() << '\n';    
    }
 
    // Bad, each shared_ptr thinks it's the only owner of the object
    std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
    std::shared_ptr<Bad> bp2 = bp1->getptr();
    std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
} // UB: double-delete of Bad

总结两条:

  1. 不能使用 std::shared_ptr(this) 来传递智能指针,否则会造成不可预估的错误
  2. 继承 std::enable_shared_from_this 的类必须使用智能指针管理

注意:

  1. 不能在继承了 std::enable_shared_from_this 的类的构造函数和析构函数中使用 shared_from_this 方法,否则会造成不可预期的错误
  2. 注意 std::enable_shared_from_this 需要被公有继承,如果使用 class 必须 public 继承
  3. 为避免循环引用,也可以使用 weak_from_this 来处理

我们改造下之前错误的代码

class Person : public std::enable_shared_from_this<Person>
{
public:
    Person()
    {
        std::cout << "Person ctor." << std::endl;
    };
    virtual ~Person()
    {
        std::cout << "Person dtor." << std::endl;
    }
    virtual const std::string getName() const
    {
        return "";
    }
    std::shared_ptr<Person> getPtr()
    {
        return shared_from_this();
    }
};
class ActionWear
{
public:
    ActionWear(std::shared_ptr<Person> person) : person(person)
    {
        std::cout << "Action wear ctor, person use_count = " << person.use_count() << std::endl;
    }
    ~ActionWear()
    {
        std::cout << "Action wear dtor." << std::endl;
    }
    void action()
    {
        std::cout << "wear action" << std::endl;
    }
    std::shared_ptr<Person> getPerson()
    {
        std::cout << "Action wear get person, person use_count = " << person.use_count() << std::endl;
        return person;
    }
private:
    std::shared_ptr<Person> person;
};
class Worker : public Person
{
public:
    Worker()
    {
        std::cout << "Worker ctor." << std::endl;
    }
    ~Worker()
    {
        std::cout << "Worker dtor." << std::endl;
    }
    void initActions()
    {
        actionWear = std::make_shared<ActionWear>(this->getPtr());
    }
    const std::string getName() const override
    {
        return "Worker";
    }
    std::shared_ptr<ActionWear> getActionWear()
    {
        return actionWear;
    }
protected:
    std::shared_ptr<ActionWear> actionWear;
};

运行结果:

Person ctor.
Worker ctor.
Action wear ctor, person use_count = 3

Process finished with exit code 0

我们发现虽然运行结束没有异常,但是没有看到析构过程,我们回到上面的代码,发现 Worker 和 ActionWear 出现了循环引用的情况,我们就按照之前那边博文去修改 ActionWear。

class ActionWear
{
public:
    ActionWear(std::shared_ptr<Person> person) : person(person)
    {
        std::cout << "Action wear ctor, person use_count = " << person.use_count() << std::endl;
    }
    ~ActionWear()
    {
        std::cout << "Action wear dtor." << std::endl;
    }
    void action()
    {
        std::cout << "wear action" << std::endl;
    }
    std::shared_ptr<Person> getPerson()
    {
        std::cout << "Action wear get person, person use_count = " << person.use_count() << std::endl;
        return person.lock();
    }
private:
    std::weak_ptr<Person> person;
};

运行后,结果如下

Person ctor.
Worker ctor.
Action wear ctor, person use_count = 2
Worker dtor.
Action wear dtor.
Person dtor.

Process finished with exit code 0

到这里我们看到程序一切运行正常。

关于 std::enable_shared_from_this 的特性介绍到这儿,欢迎读者留言补充!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值