前言
毫无疑问,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 了。我们来捋下整个过程。
- ActionWear 构造时,将 std::shared_ptr(this) 封装,此时该 shared_ptr 引用计数是 1(这个引用计数跟之前 std::make_shared 的并没有关系)
- 传入 ActionWear 构造函数赋值给 ActionWear 的 person 成员,此时 shared_ptr 引用计数是 2
- 构造函数结束,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
总结两条:
- 不能使用 std::shared_ptr(this) 来传递智能指针,否则会造成不可预估的错误
- 继承 std::enable_shared_from_this 的类必须使用智能指针管理
注意:
- 不能在继承了 std::enable_shared_from_this 的类的构造函数和析构函数中使用 shared_from_this 方法,否则会造成不可预期的错误
- 注意 std::enable_shared_from_this 需要被公有继承,如果使用 class 必须 public 继承
- 为避免循环引用,也可以使用 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 的特性介绍到这儿,欢迎读者留言补充!