一  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_ptrself_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. 如何使用?—— 具体步骤

  1. 让你的类公有继承 std::enable_shared_from_this<T>,其中 T 是你的类名。

  2. 必须使用 std::shared_ptr 来管理你的对象。这是关键前提,因为 enable_shared_from_this 内部的弱指针需要在构造时由 shared_ptr 来初始化。

  3. 在需要获取自身 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. 重要规则和注意事项

  1. 调用时机(极其重要!)不能在构造函数中调用 shared_from_this()

    • 原因:在构造函数执行时,外部的 std::shared_ptr 还没有完全构造完毕,enable_shared_from_this 内部的弱指针尚未被设置。此时调用会抛出 std::bad_weak_ptr 异常。

  2. 调用前提:在调用 shared_from_this() 之前,必须至少有一个已存在的、管理该对象的 std::shared_ptr

    • 这意味着你的对象绝不能是栈上的自动变量(例如 GoodClass obj;)。

    • 不能是通过 new 创建原始指针然后直接使用的对象。它必须从一开始就被 shared_ptr 所拥有。

  3. 拷贝和移动:由于 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,会出现几个关键问题:

  1. 二义性调用:编译器不知道应该使用哪个基类的 shared_from_this() 方法

  2. 未定义行为:即使通过明确指定解决了二义性,也可能导致未定义行为

  3. 控制块冲突:每个 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;
}

潜在问题与风险

  1. 未定义行为:C++标准没有明确规定多个 enable_shared_from_this 基类应该如何工作,不同编译器可能有不同实现

  2. 控制块不一致:每个 enable_shared_from_this 基类可能尝试管理自己的控制块,导致冲突

  3. 二义性解析:必须明确指定使用哪个基类的 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(); // 使用自定义实现
        // ...
    }
};

总结与建议

  1. 避免多重继承:尽可能避免从多个使用 enable_shared_from_this 的基类派生

  2. 单一职责原则:确保每个类只有一个明确的智能指针管理策略

  3. 谨慎设计:如果必须使用多重继承,考虑使用虚继承或自定义实现

  4. 明确指定:如果确实需要,明确指定使用哪个基类的 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. 为什么这仍然是糟糕的设计

尽管技术上可行,但这种设计有几个严重问题:

  1. 违反类契约:继承 std::enable_shared_from_this 表明这个类设计意图是被 std::shared_ptr 管理的。在栈上使用它违反了这一设计约定。

  2. 维护风险

    • 其他开发人员可能不知道这个限制,意外调用了危险方法

    • 未来添加的新方法可能使用 shared_from_this(),而您不知道

    • 代码重构时可能不小心引入了对危险方法的调用

  3. 资源浪费std::enable_shared_from_this 包含了一个 std::weak_ptr 成员,这在栈对象上是完全无用的,只是浪费空间。

  4. 混淆的类接口:类的使用者会困惑为什么某些方法可以调用而另一些不能。

更好的设计方案

方案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

  1. = default 允许内部访问:工厂方法 create() 是类的成员,可以访问私有构造函数

  2. = delete 禁止所有访问:完全删除构造函数后,连类的成员也无法调用它

  3. 设计意图不同

    • = 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; 会完全禁止构造,包括工厂方法内部的构造,因此不适合这种场景。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐