【问题标题】:Getting raw pointer from shared_ptr to pass it to function that requires raw从 shared_ptr 获取原始指针以将其传递给需要原始的函数
【发布时间】:2019-11-09 14:12:26
【问题描述】:

好的,首先我对 C++ 很陌生,所以如果我的理解很差,我深表歉意。我会尽力解释自己。我所拥有的是我正在使用一个返回 std::shared_ptr<SomeObject> 的库函数,然后我有一个不同的库函数,它采用原始指针参数(更具体地说是 node-addon-api Napi::External<T>::New(Napi::Env env, T *data) 静态函数)。我想使用我的 std::shared_ptr 创建一个Napi::External 对象。我目前正在做的是这样的:

{
    // ...
    std::shared_ptr<SomeObject> pSomeObject = something.CreateSomeObject();
    auto ext = Napi::External<SomeObject>::New(info.Env(), pSomeObject.get());
    auto instance = MyNapiObjectWrapper::Create({ ext });
    return instance;
}

但我担心这会遇到内存问题。 我的 pSomeObject 只存在于当前范围内,所以我想在返回之后应该发生什么,它的引用计数将下降到 0 并且它指向的 SomeObject 实例将被销毁,因此我会遇到我返回的实例的问题使用这个对象。但是我已经能够从我的实例中运行此代码并在 SomeObject 上调用函数,所以我想我的理解可能是错误的。

我的问题是当给定一个共享指针但由于其他第三方库的要求我需要处理一个原始指针时我应该怎么做?向我提出的一个选项是制作对象的深层副本并创建指向该对象的指针

如果我对此的理解有误,请纠正我,因为我说过我对 C++ 很陌生。

=============================

编辑:

所以我的原始帖子信息中缺少有关所有权以及该块到底是什么的信息。该块是我对Napi::ObjectWrap 实例的实现的实例方法。这个实例方法需要返回一个Napi::Object,它可以在node.js中被调用者使用。我正在使用Napi::External,因为我需要在创建返回的Napi:Object 时将Napi::Value 的子类型传递给构造函数New 函数,并且我需要在我提取的外部包装SomeObject 对象我的MyNapiObjectWrapper 构造函数是这样的:

class MyNapiObjectWrapper
{
private:
    SomeObject* someObject;
    static Napi::FunctionReference constructor; // ignore for now
public:
    static void Init(Napi::Env env) {...}
    MyNapiObjectWrapper(const CallbackInfo& info)
    {
        Napi::Env env = info.Env();
        Napi::HandleScope scope(env);

        // My original code to match the above example
        this->someObject = info[0].As<const Napi::External<SomeObject>>().Data();
    }

    DoSomething()
    {
        this->someObject->DoSomething();
    }
}

从那以后我意识到我可以在创建外部时传递共享指针的地址并按如下方式使用它

// modified first sample
{{
    // ...
    std::shared_ptr<SomeObject> pSomeObject = something.CreateSomeObject();
    auto ext = Napi::External<SomeObject>::New(info.Env(), &pSomeObject);
    auto instance = MyNapiObjectWrapper::Create({ ext });
    return instance;
}

// modified second sample
class MyNapiObjectWrapper
{
private:
    std::shared_ptr<SomeObject> someObject;
    static Napi::FunctionReference constructor; // ignore for now
public:
    static void Init(Napi::Env env) {...}
    MyNapiObjectWrapper(const CallbackInfo& info)
    {
        Napi::Env env = info.Env();
        Napi::HandleScope scope(env);

        // My original code to match the above example
        this->someObject = 
            *info[0].As<const Napi::External<std::shared_ptr<SomeObject>>>().Data();
    }

    DoSomething()
    {
        this->someObject->DoSomething();
    }
}

所以现在我传递一个指向 shared_ptr 的指针来创建我的Napi::External,我现在的问题是这样可以吗?就像我在开始时所说的那样,我是 C++ 新手,但这似乎有点异味。但是我通过一些调试对其进行了测试,并且可以看到引用计数上升,所以我想我很清楚???

【问题讨论】:

  • 如果不查看您没有/不能包含的代码,我们就无法知道对象的所有权。显然,pSomeObject 的对象所有者在块的末尾被销毁。如果调用的任何方法都没有预料到,那么您将拥有 UB。但是,如果他们深复制pSomeObject 指向的对象,一切都会好的。它是阅读文档时间寻找所有权信息。
  • 我不确定CreateSomeObject 函数中发生了什么,它们必须保持对象的所有权,因为 SomeObject 实例在块之后没有被销毁。最后发生的事情是我创建了External 来包装一些对象,然后我将外部传递给Napi::ObjectWrapper(在上面的示例中为MyNapiObjectWrapper),因此我可以提取指针并将其保留为成员变量。我可以看到为什么我的代码是错误的,但我还看不出我应该如何修复它。

标签: c++ memory-management shared-ptr smart-pointers node-addon-api


【解决方案1】:

这里是documentation的重要部分:

Napi::External 模板类实现了使用任意 C++ 数据创建 Napi::Value 对象的能力。 管理任意 C++ 数据的内存是用户的责任

所以你需要确保data传递给Napi::External Napi::External::New的对象退出,直到Napi::External&lt;T&gt;对象被破坏。

所以你显示的代码不正确。

您可以做的是将Finalize 回调传递给New 函数:

static Napi::External Napi::External::New(napi_env env,
                    T* data,
                    Finalizer finalizeCallback);

并使用 lambda 函数作为 Finalize,该 lambda 可以通过捕获到共享指针来保存一份副本,从而使共享指针保持活动状态,直到调用 finalize。

std::shared_ptr<SomeObject> pSomeObject = something.CreateSomeObject();
auto ext = Napi::External<SomeObject>::New(
                  info.Env(), 
                  pSomeObject.get(),
                  [pSomeObject](Env /*env*/, SomeObject* data) {});

【讨论】:

  • 那行得通,谢谢。虽然我认为我必须更改我的 ObjectWrapper 代码。我目前在构造函数中获取 External 并提取 SomeObject 指针,然后将 SomeObject 指针存储为成员变量。我必须将外部存储为我的成员变量,以便在我的实例被破坏之前外部不会被破坏。
  • @NickHyland 我不明白你的意思。捕获 lambda ([pSomeObject]) 会创建 pSomeObject 的副本,并且只要 lambda 存在,该副本就会保持活动状态,并且应该一直保持到调用 Finalize 回调。
  • 抱歉,我实际上并没有在该评论中明确表达自己,或者可能是我的原始帖子。在 MyNapiObjectWrapper 传递到的 MyNapiObjectWrapper 的构造函数中,我只是提取 SomeObject 指针并将其存储为成员变量。我没有对传递给它的 External 的引用,所以我想当 JS 垃圾收集器启动时,终结器方法会被调用。然而,我意识到我的整体设计可能有缺陷,所以我已经改变了起来我的实际原始实现。感谢您的帮助
  • @NickHyland 你不能从 shared_ptr 降级到托管原始指针。没有像unique_ptr 那样的release。如果您的something.CreateSomeObject 返回一个unique_ptr - 这应该是创建工厂函数时的首选 - 那么您将能够释放该对象,使其不属于unique_ptr。在您的情况下,实际上不可能说出如何解决它,因为确切的用例尚不清楚。目前尚不清楚您为什么/如何使用Napi::External,如何处理SomeObject 的所有权,...
  • @NickHyland 如果您需要使用Napi::Externalshared_ptr 从应用程序的一部分传递给另一个,那么您可以使用成员shared_ptr&lt;SomeObject&gt; valueNapi::External&lt;SharedPtrWrapper&gt;::New 创建包装器,但正如我用给定的信息所说的那样,真的不可能说出来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-15
  • 2021-01-19
  • 1970-01-01
  • 1970-01-01
  • 2021-03-10
相关资源
最近更新 更多