【问题标题】:Why does enable_shared_from_this crash if inheritance is not public instead of erroring out如果继承不是公开的而不是出错,为什么 enable_shared_from_this 会崩溃
【发布时间】:2019-06-10 15:59:18
【问题描述】:

我在一个项目中使用了 shared_ptr。在某一时刻,我不得不将原始指针存储为 void,然后在传递 void* 的回调中将其转换回其 shared_ptr 形式。但由于某种原因,代码不断崩溃。我不明白为什么,因为我没有收到任何编译器错误或警告。但我注意到,当我从std::enable_shared_from_this 继承时,我并没有将其指定为公共继承。这就是导致崩溃的原因。

我写了一个示例代码,我只是想知道为什么会这样。

#include <memory>
#include <iostream>

class TestShared : public std::enable_shared_from_this<TestShared>{
private:
    int32_t id;
public:
    TestShared(int32_t id){
        this->id = id;
    }
    std::shared_ptr<TestShared> getshared(){
        return shared_from_this();
    }
    int32_t getid(){
        return id;
    }
};

int main(){
    std::shared_ptr<TestShared> ts(new TestShared(0xFF));
    void* tsp = ts.get();
    std::shared_ptr<TestShared> tsn = ((TestShared*)tsp)->getshared();
    std::cout << std::hex << tsn->getid();
    return 0;
}

这样代码就能正常执行和运行,我得到了预期的结果。

但是当我从继承中删除 public 时:

#include <memory>
#include <iostream>

class TestShared : std::enable_shared_from_this<TestShared>{
private:
    int32_t id;
public:
    TestShared(int32_t id){
        this->id = id;
    }
    std::shared_ptr<TestShared> getshared(){
        return shared_from_this();
    }
    int32_t getid(){
        return id;
    }
};

int main(){
    std::shared_ptr<TestShared> ts(new TestShared(0xFF));
    void* tsp = ts.get();
    std::shared_ptr<TestShared> tsn = ((TestShared*)tsp)->getshared();
    std::cout << std::hex << tsn->getid();
    return 0;
}

然后它会导致崩溃。那么为什么public 在这里有所作为,为什么编译器不给出警告/错误呢?

【问题讨论】:

  • 无关:为什么需要将拥有的指针转换为void*?它与shared_ptr 并排看起来非常格格不入。
  • 因为我是一个使用 C 库的人,它触发了我实现的回调。传递给回调的对象有一个用户上下文,它是一个 void*。所以我将我的对象转换为 void* 作为要在回调中传递的上下文。这个特定示例中的 void* 只是为了准确复制我在项目中所做的事情。
  • 完全有道理;继续。我只是担心你可能在做某种类型的双关语或多态性黑客,但似乎你知道你在做什么:-)

标签: c++ shared-ptr private-inheritance enable-shared-from-this


【解决方案1】:

public 很重要,因为shared_ptr 系统需要访问给定类型的enable_shared_from_this 基类。如果不能从给定类型访问 publicly,它就不能这样做。

对于不可访问的基类没有警告/错误,因为系统无法知道您的代码错误。

即使enable_shared_from_this 是私有的,使用可以“启用shared_from_this”的shared_ptr 构造函数在概念上是可以的。为什么?考虑以下几点:

class B : public enable_shared_from_this<B> {...};

class D : private B {...};

现在,B 期望能够做shared_from_this 体操。但是D 私下继承了它。所以DB(以及B::shared_from_this)的关系是私有的。也许D 使用B 的方式不会触发任何shared_from_this 用法。

所以如果D 不依赖B::shared_from_this,如果B 只是D 的一个实现细节,为什么有人将D 放在shared_ptr 中会出错?

您想出的任何测试都不会导致这样的误报。因此,如果enable_shared_from_this 基类不可访问,那么可以尝试使用它的shared_ptr 构造函数就不要尝试使用它。

【讨论】:

  • 嗯,好吧,这是继承和语义本身的问题。不一定与shared_ptr有关?
  • @Nina 是的,没错。 private 一般具有“实现细节;看远不碰”的语义。因此,不能查询一个类的私有继承对象是有道理的。
  • 我认为,如果你enable_shared_from_this,那是因为你想从一个类方法中获得一个指向this 的共享指针。我不知道拥有一个可以同时处理共享和非共享指针的类是很常见的。我想我宁愿有误报。
  • > shared_ptr 系统需要访问给定类型的enable_shared_from_this 基类。这是如何实现的?
  • @B_Dex_Float:显而易见的方式:shared_ptrenable_shared_from_this 的朋友,因此它可以访问其内部并根据需要进行操作。这就是为什么您的类型需要将enable_shared_from_this 设为public 基类:以便它可以将指向您的类型的指针转​​换为指向enable_shared_from_this 基类的指针。
【解决方案2】:

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

enable_shared_from_this 的一个常见实现是持有对this 的弱引用(例如std::weak_ptr)。 std::shared_ptr 的构造函数检测到是否存在明确且可访问的(C++17 起)enable_shared_from_this 基,并将新创建的 std::shared_ptr 分配给内部存储的弱引用,如果它还没有被实时 std::shared_ptr 拥有(从 C++17 开始)。

如果继承是公共的,那么在初始化ts 时,它会在enable_shared_from_this 基础子对象中“记录”它是TestShared 对象的所有者。稍后调用 getshared 时,会参考基础子对象,并创建一个与 ts 共享所有权的新 shared_ptr 对象。

如果继承不是公开的,那么在初始化ts 时,它不会“知道”有一个需要写入的enable_shared_from_this 子对象。因此,当调用getshared 时,enable_shared_from_this 子对象不包含有关当前谁拥有该对象的任何信息。在 C++17 中,这会导致异常;在 C++17 之前,结果是未定义的。

【讨论】:

  • 如果我有一个类允许从它继承,我的类怎么知道子类是公共继承还是私有继承?我猜 enable_shared_from_this 只允许在 public 下调用 shared_from_this() 而不是 private?
  • @Nina 对不起,我不明白这个:“如果子类是做公共继承或私有继承,我的班级会怎样”
  • 对不起,我在评论中打错了。 “我的班级怎么知道”。
  • @Nina 一个类无法知道在继承派生类时指定了什么样的访问权限,因此不需要知道。如果有人选择使用私有继承,他们是不使用enable_shared_from_this的责任。
猜你喜欢
  • 2018-04-27
  • 1970-01-01
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-14
  • 1970-01-01
相关资源
最近更新 更多