【问题标题】:How to interface between a foreign language and a C++ library which returns shared pointers如何在外语和返回共享指针的 C++ 库之间进行接口
【发布时间】:2019-06-05 20:13:08
【问题描述】:

我正在编写一个库,它在 kdb+(尽管这个问题通常适用于外语接口)和一个 C++ 库之间进行接口,其中大多数 API 调用返回一个std::shared_ptr。在使用 kdb+ 与大多数库交互时,通常使用库的 API 创建对象,然后将其原始指针作为 long long 返回,以便 kdb+ 程序员可以根据自己的选择将对象发送回库。

共享指针使这变得困难。最小的例子:

extern "C" K k_new_foo() {
    // not using auto for the sake of clarity in the example
    std::shared_ptr<library::Foo> ptr = library::Foo::Create();
    // return the raw pointer as a long long int in a kdb object
    return kj(reinterpret_cast<long long>(ptr.get()));
}
// ptr goes out of scope -> the library::Foo is freed prematurely 

我想知道是否有某种方法可以无限期延长std::shared_ptr 的生命周期,或者以其他方式防止破坏它指向的数据,直到程序员使用另一个调用从 kdb+ 中手动释放它到这个接口库。我很清楚我所要求的违背了使用智能指针的目的;我很想知道一种的方法来处理这个问题,如果这样的事情存在并且是实用的。

【问题讨论】:

  • 您是否考虑过“新建”一个新的 shared_ptr 并将其传递给其他语言?
  • 泄露共享 ptr 的原始 ptr 是危险的。
  • 可能有办法做到这一点,但我非常怀疑它们中的任何一种都符合的条件。您想明确规避library(共享所有权管理)明确实施的安全机制。你坚定地致力于进入糟糕的代码领域。
  • 我不期待一些神奇的完美解决方案,但问也无妨。
  • @WillDaSilva 我当然不是要批评这个问题。有时您会发现自己处于需要任何解决方案的困境中,即使是糟糕的解决方案。我的意思是评论最后一句话,我认为可能没有任何方法,只有方法

标签: c++ c++17 shared-ptr smart-pointers kdb


【解决方案1】:

延长此类共享对象的生命周期的唯一方法是将shared_ptr 存储在内存中,直到不再需要该共享对象。

例如,您可以new 一个单独的std::shared_ptr 并将其返回为外语,然后在使用完毕后delete 它:

using Foo_sharedptr = std::shared_ptr<library::Foo>;

extern "C" K k_new_foo() {
    Foo_sharedptr ptr = library::Foo::Create();
    Foo_sharedptr *ptr2 = new Foo_sharedptr(ptr);
    return kj(reinterpret_cast<J>(ptr2));
}

extern "C" void k_free_foo(K foo) {
    delete reinterpret_cast<Foo_sharedptr*>(foo->j);
    r0(foo);
}

或者,您可以将shared_ptr 存储在您拥有的全局容器中,然后传递引用其元素的值:

using Foo_ptr = library::Foo*;
using Foo_sharedptr = std::shared_ptr<library::Foo>;
using FooMap = std::map<Foo_ptr, Foo_sharedptr>;

static FooMap g_foos;
// wrapped with a std::mutex if you need multithread safety...

extern "C" K k_new_foo() {
    Foo_sharedptr foo = library::Foo::Create();
    Foo_ptr ptr = foo.get();
    g_foos[ptr] = foo;
    return kj(reinterpret_cast<J>(ptr));
}

extern "C" void k_free_foo(K foo) {
    Foo_ptr ptr = reinterpret_cast<Foo_ptr>(foo->j);
    FooMap::iterator iter = g_foos.find(ptr);
    if (iter != g_foos.end()) g_foos.erase(iter);
    r0(foo);
}

【讨论】:

    【解决方案2】:

    我明白你的意思,不得不经历那是一团糟。

    假设您不仅有一个新方法,而且还有一个显式删除方法。在这种情况下,您可以分配一个 shared_ptr。

    extern "C" K k_new_foo() {
        // not using auto for the sake of clarity in the example
        std::shared_ptr<library::Foo> ptr = library::Foo::Create();
        // return the raw pointer as a long long int in a kdb object
        return kj(reinterpret_cast<long long>(new std::shared_ptr<library::Foo>(ptr)));
    }
    
    extern "C" void k_delete_foo(K k) {
         delete reinterpret_cast<std::shared_ptr<library::Foo> *>(k.foo));
    }
    
    extern "C" void k_thread_foo(K k) {
         auto &ptr = *reinterpret_cast<std::shared_ptr<library::Foo> *>(k.foo));
         std::thread{[ptr]{ /* Do something */ }}.detach();
    }
    

    当然,这不是一种好的编码方式。但是,它确实允许您为所有 C++ 代码保留 shared_ptr,同时在从另一种语言错误地使用时可能会出现内存泄漏/ub。

    如果您想防止错误使用。您可以将所有指针收集在某种全局变量中。这样,您可以首先检查该指针是否与其他语言共享。您可以与其共享原始指针,而不是传递新的 shared_ptr,并且行为可以变得更加明确,因为在退出时,将调用所有析构函数。缺点包括更多的簿记,这将作为性能损失而引人注目。

    如果您只想将所有权附加到您传递的原始指针,而没有这样的删除器方法,我只能通过将所有内容收集在全局向量中来向您推荐某种内存泄漏。

    【讨论】:

    • 我希望看到一个中间转换为 intptr_t,然后将其转换为 long long,并使用一些 static_asserts 进行大小检查,但您的建议很优雅。
    • @Bathsheba 我同意这一点,但是,如果不咨询编译器,我无法编写 100% 正确的代码
    【解决方案3】:

    您可以做的是在您的界面中维护一个shared_ptr&lt;Foo&gt; 的容器。您的 k_new_foo 函数会将 shared_ptr 的副本保存在该容器中。然后k_delete_foo 函数将从容器中删除 shared_ptr。

    如果有太多不同的对象类型需要跟踪,这将不起作用。

    【讨论】:

    • 您只需要为其他语言提供一种方法来引用该容器中的项目。我想k_new_foo 可以返回shared_ptr 的原始指针作为参考值,然后k_delete_foo 可以在容器中搜索该指针
    • 如果您只是让对象保持活动状态,您不能只使用shared_ptr&lt;void&gt; 的容器来不跟踪每种类型的对象吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-07
    • 1970-01-01
    • 2013-12-24
    • 1970-01-01
    • 2013-07-13
    • 2013-10-12
    相关资源
    最近更新 更多