c++中shared_from_this的用法(deepseek教我学c++)
摘要:std::enable_shared_from_this和shared_from_this()用于解决在对象内部安全获取自身shared_ptr的问题。直接使用this构造新shared_ptr会导致双重删除,而通过继承enable_shared_from_this并调用shared_from_this(),可获取与外部shared_ptr共享控制块的指针。使用时需确保对象已被shared_
一 std::enable_shared_from_this 和 shared_from_this() 的用法
好的,我们来详细讲解一下 std::enable_shared_from_this 和 shared_from_this() 的用法。
1. 为什么需要它?—— 核心问题
首先,要理解它的用途,我们必须先理解一个问题场景。
假设你有一个由 std::shared_ptr 管理的对象,在这个对象的内部(即某个成员函数中),你需要将一个指向自身的指针(通常也是 std::shared_ptr)传递给其他函数或对象。
错误的做法:
cpp
复制
下载
class BadClass {
public:
void doSomething() {
// 错误!这会创建一个新的控制块,导致双重删除(double free)
std::shared_ptr<BadClass> self_ptr(this);
someOtherFunction(self_ptr); // 传递出去
}
};
int main() {
std::shared_ptr<BadClass> ptr1(new BadClass());
ptr1->doSomething(); // 灾难发生!
return 0;
}
为什么错误?ptr1 已经通过 new 创建了一个控制块来管理这个 BadClass 对象。在 doSomething() 内部,你又用 this 指针构造了另一个 shared_ptr(self_ptr)。这个新的 shared_ptr 完全不知道 ptr1 的存在,它会为自己管理的 this 对象再创建一个新的控制块。
当 ptr1 和 self_ptr 这两个智能指针的生命周期结束时,它们都会试图去删除同一个对象,这就导致了未定义行为(通常是程序崩溃,双重删除)。
2. std::enable_shared_from_this 的解决方案
std::enable_shared_from_this 就是为了解决上述问题而设计的。它允许一个对象安全地生成一个与已有共享所有权(即已有的 shared_ptr)共享同一个控制块的 std::shared_ptr 指向自己。
工作原理:
当你从一个继承自 std::enable_shared_from_this<T> 的类创建 shared_ptr 时,shared_ptr 的构造函数会探测到这个基类,并在这个基类内部弱性地存储一个指向刚刚创建的控制块的“弱引用”(weak_ptr)。之后,你就可以通过调用 shared_from_this() 成员函数来获取一个共享同一控制块的 shared_ptr。
3. 如何使用?—— 具体步骤
-
让你的类公有继承
std::enable_shared_from_this<T>,其中T是你的类名。 -
必须使用
std::shared_ptr来管理你的对象。这是关键前提,因为enable_shared_from_this内部的弱指针需要在构造时由shared_ptr来初始化。 -
在需要获取自身
shared_ptr的成员函数中,调用shared_from_this()。
正确示例:
cpp
复制
下载
#include <memory>
#include <iostream>
class GoodClass : public std::enable_shared_from_this<GoodClass> {
public:
void doSomething() {
// 正确!返回一个与已有共享所有权共享控制块的 shared_ptr
std::shared_ptr<GoodClass> self_ptr = shared_from_this();
someOtherFunction(self_ptr); // 安全地传递出去
// 也可以直接传递:someOtherFunction(shared_from_this());
}
void someOtherFunction(std::shared_ptr<GoodClass> ptr) {
std::cout << "Use count: " << ptr.use_count() << std::endl;
}
};
int main() {
// 关键:必须用 shared_ptr 管理,不能用栈对象或 unique_ptr
std::shared_ptr<GoodClass> ptr1 = std::make_shared<GoodClass>();
ptr1->doSomething(); // 输出 Use count: 2 (ptr1 和 self_ptr)
// GoodClass bad_obj; // 错误!不能在栈上创建,否则 shared_from_this() 会抛异常
// bad_obj.doSomething();
return 0;
}
4. 重要规则和注意事项
-
调用时机(极其重要!):不能在构造函数中调用
shared_from_this()。-
原因:在构造函数执行时,外部的
std::shared_ptr还没有完全构造完毕,enable_shared_from_this内部的弱指针尚未被设置。此时调用会抛出std::bad_weak_ptr异常。
-
-
调用前提:在调用
shared_from_this()之前,必须至少有一个已存在的、管理该对象的std::shared_ptr。-
这意味着你的对象绝不能是栈上的自动变量(例如
GoodClass obj;)。 -
也不能是通过
new创建原始指针然后直接使用的对象。它必须从一开始就被shared_ptr所拥有。
-
-
拷贝和移动:由于
enable_shared_from_this内部存储了与对象实例关联的状态,所以它的拷贝和移动语义需要小心处理。通常建议将这类类设为不可拷贝或不可移动,或者仔细实现拷贝/移动构造函数。
5. 典型应用场景
-
异步操作和回调:这是最常见的场景。在启动一个异步操作(如线程、网络回调)时,你需要保证回调函数被执行时,它所依赖的对象仍然存活。通过将
shared_from_this()捕获到回调函数的lambda表达式中,可以延长对象的生命周期。cpp
复制
下载void startAsyncOperation() { // 捕获 shared_from_this() 来保证对象在异步操作期间存活 auto self = shared_from_this(); std::async(std::launch::async, [self]() { // 使用 self->some_member_... // 即使外部的 shared_ptr 已析构,只要这个异步操作没完成,对象就活着 }); } -
内部数据结构:对象需要将自身插入到某个需要
shared_ptr来管理的容器或观察者列表中。 -
链式调用:在实现流畅接口(Fluent Interface)时,有时需要返回对象自身的
shared_ptr。cpp
复制
下载std::shared_ptr<GoodClass> setValue(int value) { value_ = value; return shared_from_this(); // 返回自身指针用于链式调用 }
总结
| 关键点 | 说明 |
|---|---|
| 目的 | 在对象内部安全地获取一个指向自身的 shared_ptr,与外部已有的 shared_ptr 共享所有权。 |
| 方法 | 公有继承 std::enable_shared_from_this<T>,并调用 shared_from_this() 成员函数。 |
| 前提 | 对象必须已被一个 std::shared_ptr 管理(例如 make_shared)。 |
| 禁止 | 不能在构造函数中调用。不能用于栈对象。 |
| 场景 | 异步回调、延长生命周期、链式调用、注册到容器。 |
希望这个解释能帮助你彻底理解 shared_from_this 的用法!
二 多重继承下的 std::enable_shared_from_this
当一个类从多个基类派生,且这些基类都使用了 std::enable_shared_from_this 时,情况会变得复杂。让我们详细分析这种情况。
问题分析
在多重继承场景下,如果多个基类都继承自 std::enable_shared_from_this,会出现几个关键问题:
-
二义性调用:编译器不知道应该使用哪个基类的
shared_from_this()方法 -
未定义行为:即使通过明确指定解决了二义性,也可能导致未定义行为
-
控制块冲突:每个
enable_shared_from_this基类都期望有自己的控制块引用
代码示例与问题展示
cpp
#include <memory>
#include <iostream>
class Base1 : public std::enable_shared_from_this<Base1> {
public:
virtual ~Base1() = default;
void func1() {
std::cout << "Base1::func1\n";
}
};
class Base2 : public std::enable_shared_from_this<Base2> {
public:
virtual ~Base2() = default;
void func2() {
std::cout << "Base2::func2\n";
}
};
class Derived : public Base1, public Base2 {
public:
void tryGetShared() {
// 错误:对「shared_from_this」的调用有歧义
// auto self = shared_from_this();
// 可以明确指定使用哪个基类的方法
auto self1 = Base1::shared_from_this(); // 返回 std::shared_ptr<Base1>
auto self2 = Base2::shared_from_this(); // 返回 std::shared_ptr<Base2>
std::cout << "self1 use_count: " << self1.use_count() << std::endl;
std::cout << "self2 use_count: " << self2.use_count() << std::endl;
// 但这些指针可能不共享同一个控制块,导致未定义行为
}
};
int main() {
auto derived_ptr = std::make_shared<Derived>();
derived_ptr->tryGetShared();
return 0;
}
潜在问题与风险
-
未定义行为:C++标准没有明确规定多个
enable_shared_from_this基类应该如何工作,不同编译器可能有不同实现 -
控制块不一致:每个
enable_shared_from_this基类可能尝试管理自己的控制块,导致冲突 -
二义性解析:必须明确指定使用哪个基类的
shared_from_this(),但这可能不是你想要的行为
解决方案
1. 避免多重继承 enable_shared_from_this
最好的解决方案是重新设计类层次结构,避免多个基类都使用 enable_shared_from_this。
2. 使用虚继承(有限解决方案)
cpp
class Base1 : public virtual std::enable_shared_from_this<Base1> {
// ...
};
class Base2 : public virtual std::enable_shared_from_this<Base1> { // 注意:这里使用Base1
// ...
};
class Derived : public Base1, public Base2 {
public:
void tryGetShared() {
// 现在可以无歧义地调用
auto self = shared_from_this(); // 返回 std::shared_ptr<Base1>
}
};
但这种方法有局限性,因为它要求所有基类共享同一个 enable_shared_from_this 基类。
3. 手动实现类似功能
如果必须使用多重继承,可以考虑手动实现类似功能:
cpp
class Derived : public Base1, public Base2 {
private:
std::weak_ptr<Derived> weak_this_;
public:
static std::shared_ptr<Derived> create() {
auto ptr = std::make_shared<Derived>();
ptr->weak_this_ = ptr;
return ptr;
}
std::shared_ptr<Derived> shared_from_this() {
return weak_this_.lock();
}
void tryGetShared() {
auto self = shared_from_this(); // 使用自定义实现
// ...
}
};
总结与建议
-
避免多重继承:尽可能避免从多个使用
enable_shared_from_this的基类派生 -
单一职责原则:确保每个类只有一个明确的智能指针管理策略
-
谨慎设计:如果必须使用多重继承,考虑使用虚继承或自定义实现
-
明确指定:如果确实需要,明确指定使用哪个基类的
shared_from_this()方法
最佳实践:在大多数情况下,应该重构代码,确保只有一个基类使用 std::enable_shared_from_this,或者使用组合而非继承来实现所需功能。
三 栈上对象与 std::enable_shared_from_this 的关系
您提出了一个很好的问题。是的,从技术上讲,如果您的栈上对象从不调用任何使用 shared_from_this() 的成员函数,那么程序不会出现运行时错误。但是,这仍然是一种有风险的设计,我不建议这样做。
详细解释
1. 技术上的正确性
cpp
#include <memory>
class GoodClass : public std::enable_shared_from_this<GoodClass> {
public:
void safeFunction() {
// 这个函数不使用 shared_from_this()
// 完全可以安全调用
}
void dangerousFunction() {
// 这个函数使用了 shared_from_this()
auto self = shared_from_this(); // 如果在栈对象上调用,会抛出异常
}
};
int main() {
GoodClass stackObj; // 栈上对象
// 这是安全的 - 不会调用 shared_from_this()
stackObj.safeFunction();
// 这是危险的 - 会抛出 std::bad_weak_ptr 异常
// stackObj.dangerousFunction(); // 取消注释会导致运行时异常
return 0;
}
从技术上讲,上面的代码是安全的,只要您确保不调用 dangerousFunction()。
2. 为什么这仍然是糟糕的设计
尽管技术上可行,但这种设计有几个严重问题:
-
违反类契约:继承
std::enable_shared_from_this表明这个类设计意图是被std::shared_ptr管理的。在栈上使用它违反了这一设计约定。 -
维护风险:
-
其他开发人员可能不知道这个限制,意外调用了危险方法
-
未来添加的新方法可能使用
shared_from_this(),而您不知道 -
代码重构时可能不小心引入了对危险方法的调用
-
-
资源浪费:
std::enable_shared_from_this包含了一个std::weak_ptr成员,这在栈对象上是完全无用的,只是浪费空间。 -
混淆的类接口:类的使用者会困惑为什么某些方法可以调用而另一些不能。
更好的设计方案
方案1:完全避免栈上使用
最简单的解决方案是遵循设计意图,始终使用 shared_ptr 管理这类对象:
cpp
int main() {
// 正确的方式
auto obj = std::make_shared<GoodClass>();
obj->safeFunction();
obj->dangerousFunction(); // 现在安全了
return 0;
}
方案2:分离接口(推荐)
如果您的类有些方法需要共享所有权,有些不需要,考虑使用接口分离:
cpp
// 不需要共享所有权的功能放在基类中
class GoodClassBase {
public:
void safeFunction() {
// 安全的方法实现
}
};
// 需要共享所有权的功能放在派生类中
class GoodClass : public GoodClassBase,
public std::enable_shared_from_this<GoodClass> {
public:
void dangerousFunction() {
auto self = shared_from_this();
// 使用共享指针
}
};
int main() {
// 栈上只能使用基类功能
GoodClassBase stackObj;
stackObj.safeFunction();
// 堆上可以使用全部功能
auto heapObj = std::make_shared<GoodClass>();
heapObj->safeFunction();
heapObj->dangerousFunction();
return 0;
}
方案3:使用静态工厂方法
强制使用者通过特定方式创建对象:
cpp
class GoodClass : public std::enable_shared_from_this<GoodClass> {
private:
// 私有构造函数,防止栈上创建
GoodClass() = default;
public:
// 静态工厂方法,确保只能用 shared_ptr 创建
static std::shared_ptr<GoodClass> create() {
return std::make_shared<GoodClass>();
}
void safeFunction() { /* ... */ }
void dangerousFunction() { /* ... */ }
};
int main() {
// GoodClass stackObj; // 错误:构造函数不可访问
auto obj = GoodClass::create(); // 正确方式
obj->safeFunction();
obj->dangerousFunction();
return 0;
}
结论
虽然技术上可以在栈上创建继承 std::enable_shared_from_this 的对象,只要不调用使用 shared_from_this() 的方法,但这是一个糟糕的设计实践。
这样做违反了类的设计意图,增加了维护风险,并且可能导致未来的错误。更好的方法是重新设计类层次结构,或者强制使用者始终通过 shared_ptr 来管理这类对象。
最佳实践:如果一个类继承了 std::enable_shared_from_this,就应该始终使用 std::shared_ptr 来管理它的实例,避免在栈上创建。
四 GoodClass() = default; 与 GoodClass() = delete; 的区别
您提出了一个很好的问题。这两者确实有重要区别,让我详细解释一下。
GoodClass() = default; 的含义
cpp
class GoodClass {
private:
GoodClass() = default; // 使用编译器生成的默认构造函数,但设为私有
public:
// ...
};
-
= default表示"使用编译器生成的默认实现" -
这行代码告诉编译器:"请为我生成一个默认构造函数,就像我没有声明任何构造函数时你会做的那样"
-
设置为
private意味着只有类内部和友元可以调用这个构造函数
GoodClass() = delete; 的含义
cpp
class GoodClass {
public:
GoodClass() = delete; // 完全删除默认构造函数
// ...
};
-
= delete表示"完全删除这个函数" -
这行代码告诉编译器:"不允许任何人调用这个构造函数,无论在什么情况下"
-
设置为
public或private都可以,因为无论如何都无法调用
在工厂模式中的应用区别
使用 = default(正确做法)
cpp
class GoodClass : public std::enable_shared_from_this<GoodClass> {
private:
GoodClass() = default; // 私有默认构造函数
public:
static std::shared_ptr<GoodClass> create() {
return std::make_shared<GoodClass>(); // 可以调用,因为是类的成员
}
};
int main() {
// GoodClass obj; // 错误:构造函数不可访问
auto obj = GoodClass::create(); // 正确:通过工厂方法创建
return 0;
}
使用 = delete(错误做法)
cpp
class GoodClass : public std::enable_shared_from_this<GoodClass> {
public:
GoodClass() = delete; // 完全删除默认构造函数
static std::shared_ptr<GoodClass> create() {
return std::make_shared<GoodClass>(); // 错误:无法调用已删除的构造函数
}
};
为什么在工厂模式中使用 = default 而不是 = delete
-
= default允许内部访问:工厂方法create()是类的成员,可以访问私有构造函数 -
= delete禁止所有访问:完全删除构造函数后,连类的成员也无法调用它 -
设计意图不同:
-
= default+private:"只能通过特定方式创建对象" -
= delete:"完全不能以这种方式创建对象"
-
其他构造函数场景
同样的原则也适用于其他构造函数:
cpp
class Example {
private:
Example() = default; // 允许内部使用默认构造
Example(const Example&) = delete; // 完全禁止拷贝
Example(Example&&) = default; // 允许内部移动,但设为私有
public:
static Example create() {
return Example(); // 可以调用,因为是成员
}
Example clone() {
// return *this; // 错误:拷贝构造函数已删除
return Example(); // 只能创建新实例
}
};
总结
| 特性 | GoodClass() = default; |
GoodClass() = delete; |
|---|---|---|
| 可访问性 | 可以在类内部和友元中使用 | 完全不可访问,任何地方都不能使用 |
| 设计意图 | 限制构造方式,但不完全禁止 | 完全禁止某种构造方式 |
| 工厂模式 | 适合,工厂方法可以调用 | 不适合,工厂方法也无法调用 |
| 常见用途 | 实现单例模式、工厂模式 | 禁止不希望的隐式转换、拷贝等 |
在您提到的工厂模式场景中,使用 GoodClass() = default; 并将其设为 private 是正确的做法,因为它允许类的静态工厂方法创建实例,同时阻止外部直接实例化。而使用 GoodClass() = delete; 会完全禁止构造,包括工厂方法内部的构造,因此不适合这种场景。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)