【问题标题】:How to properly delete an object that is a void pointer?如何正确删除作为空指针的对象?
【发布时间】:2014-09-22 02:58:29
【问题描述】:

我正在尝试将 C++ 类(例如,class foo)连接到 C。到目前为止,我所做的是定义一个包含不透明指针成员变量(即,void*)的 C 结构,即指向关联的 C++ foo 对象。

struct C_foo {
  void *foo_obj;
};

我定义了一个alloc() C 接口函数,它分配C_foo 类型的对象:

struct C_foo* alloc(/* input */) {
  struct C_foo *out = new struct C_foo;
  out->foo_obj      = new foo(/* input */);

  return out;
};

我现在要做的是创建一个dealloc() C 接口函数,该函数将正确地释放之前分配的C_foo 类型的对象,上面显示了alloc()

void dealloc(struct C_foo *obj) {
  /* ??? */
}

我知道明确删除void* 指针(即delete obj->foo_obj;)会导致未定义的行为(§ 5.3.5/1 [expr.delete]):

delete-表达式的结果类型为 void [81]。

[81] 这意味着不能使用类型指针删除对象 void* 因为void 不是对象类型 em>。

问题:

我将如何正确释放 struct C_foo 对象?

【问题讨论】:

标签: c++ c pointers void-pointers


【解决方案1】:

如果你(确定地)知道它指向什么类型,然后强制转换:

delete static_cast<foo*>(obj->foo_obj);

如果您忘记了类型,则需要重新设计。

【讨论】:

  • 感谢您的快速回复。为什么是static_cast 而不是reinterpret_castdynamic_cast
  • 因为如果您知道正确的类型并且只想将指针转换为它,那么这是正确的转换。 reinterpret_cast 用于强制编译器进行通常不允许的转换(此处不适用),void* 中的 dynamic_cast 甚至无法编译
【解决方案2】:

如果您因为某些 C API 而需要传递不透明的句柄,并且您的对象具有完全不同的类型,则可以使用如下所述的方法。

请注意,如果您的所有类型共享一个公共基,您可以只为基提供一个虚拟析构函数,static_castvoid* 指向指向基的指针,然后delete .这是一种比我在下面概述的方法更常见的方法。

句柄结构

这需要保存一个指向 yuo 分配的对象的指针,以及一个指向编码类型的指针(以便您可以删除它):

struct Handle {
    void* handle;
    void* deleter_info;
};

C++ 实现细节

你会有一些课程;这些是您想要将句柄传递给...的实例的东西

class Foo;
class Bar;
// etc

你还需要一个删除器的基类:

struct deleter_base {
    virtual void destroy(Handle h) = 0;
    virtual ~deleter_base() {}
};

...和一个类模板来生成知道相关类型的派生类:

template<typename T> struct deleter {
    virtual void destroy(Handle h)
    {
        T* ptr = static_cast<T*>(h.handle);
        delete ptr;
    }
};

创建对象

对于您要为其提供句柄的每种类型,您都需要一个函数来创建句柄:

Handle create_foo_handle()
{
    Handle h = {0};
    h.ptr = new foo;
    h.deleter_info = new deleter<foo>;
    return h;
}

Handle create_bar_handle()
{
    Handle h = {0};
    h.ptr = new bar;
    h.deleter_info = new deleter<bar>;
    return h;
}

销毁对象

你需要一个销毁函数:

void destroy(Handle h)
{
    deleter_base* deleter = static_cast<deleter_base*>(h.deleter_info);
    deleter->destroy(h); // delete the foo, or bar, or whatever
    delete deleter; // delete the deleter
}

注意事项

该结构可以保存deleter_base* deleter_info,而不是void* deleter_info。这真的是一个品味问题,以及您是否想要在 C API 中使用 struct deleter_info;。将其存储在 void* 中会隐藏实现细节,使其真正不透明。

为了能够有意义地使用句柄,您还需要对一些其他信息进行编码,以便能够从void* handle 成员中检索有用的信息。通常,变体类型使用枚举成员执行此操作。或者,您可能希望您的用户足够聪明,只将他们的句柄传回一个需要正确类型句柄的函数。您可以使用不同的句柄结构类型(struct HandleFoo;struct HandleBar;、...)来强制执行此操作,并且仍然在内部使用 void* 成员来保持不透明度。

【讨论】:

  • 为什么 deleter_info 是 void* 而不是 deleter_base*
  • @NeilKirk:这是一个品味问题;如果你真的这样做是为了提供一个不透明的句柄作为 C API 的一部分,你不想公开任何实现细节。转换为 void* 就可以实现这一点。您也可以将其保留为 deleter_base*,但您也必须在 API 标头中添加 struct deleter_base; 声明。
  • 哦,我没有注意到关于 C 的一点!
【解决方案3】:

转换为正确的类型:

delete static_cast<foo*>(obj->foo_obj);

【讨论】:

  • 感谢您的快速回复。为什么是static_cast 而不是reinterpret_castdynamic_cast
  • static_cast 是最好的 - 在你的情况下 - 你知道对象的类型,并且强制转换反转隐式转换。存在从 foo*void* 的隐式转换 - 您在分配给 out-&gt;foo_obj 时使用了它。
【解决方案4】:

使用虚拟析构函数创建一个公共基类,并使用它来代替 void*。

您可以执行以下操作。

class GenericBase
{
public:
    virtual ~GenericBase() = 0;
};

inline GenericBase::~GenericBase() {} // or put in source file without inline

template<class T>
class GenericWrapper : public GenericBase
{
public:
    typedef T Type;
    Type x;

    GenericWrapper() {}
    GenericWrapper(const Type& x) : x(x) {}
};

您可以使用 dynamic_cast 将 GenericBase* 转换为具体类型,以便从安全检查中受益。

我刚刚注意到你想将它与 C 一起使用。显然你不能将 GenericBase* 传递给 C。但是你可以将它作为 void* 传递给 C 并在需要时转换回 GenericBase*删除它。

【讨论】:

    【解决方案5】:

    我假设您总是将相同类型的实例放入 void*

    在这种情况下,pImpl 时间:

    struct foo_impl; // note, just a name
    struct C_foo {
      foo_impl *foo_obj; // can use pointers to undefined structs in both C and C++
    };
    

    现在你的大部分问题都消失了。 C 将foo_obj 视为一个不透明的指针。

    在 C++ 中,我们包含另一个头文件(示例字段):

    // in C++ **only** header file -- C does not see this:
    struct foo_impl {
      int x;
      std::vector<double> v;
      foo_impl( int, double const* b, double const* e ); // constructor
    };
    
    // functions exposed to C, but implemented in C++ with visibility of the above foo_impl
    extern "C" struct C_foo* alloc(int x, double const* b, double const* e) {
     struct C_foo *out = new struct C_foo;
     out->foo_obj      = new foo_impl(x, b, e);
    
     return out;
    };
    
    extern "C" void dealloc(struct C_foo *obj) {
      delete obj->foo_obj;
      delete obj;
    }
    

    你赢了。

    请注意,struct 只是 C++ 中 class 的名称,默认为 public,而不是默认的 private

    我将名称从foo 更改为foo_impl,并在其中创建了一些示例数据。

    如果您可以在void* 中添加多种不同的类型,我首先建议您使用虚拟析构函数放置一个纯虚拟接口类,并基本上遵循上述步骤。


    现在,在某些情况下,您实际上希望在不透明指针中存储多个不同的、不相关的类型。这些并不常见。但在这些情况下,我们需要存储一个销毁函数。

    同样,我更喜欢我上面的方法,但如果它不起作用,我们有这个。

    deleter函数有几种存储方式:

    typedef void(*foo_deleter)(void*);
    struct C_foo {
      void* foo_obj;
      foo_deleter* deleter;
    };
    

    另一种方法是:

    struct foo_impl;
    struct C_foo {
      foo_impl* foo_obj;
    };
    
    // elsewhere:
    typedef void(*foo_deleter)(foo_impl*);
    struct foo_impl {
      foo_deleter* deleter;
    };
    
    template<typename T>
    struct foo_details {
      foo_impl header;
      T* data;
      ~foo_details() { delete data; }
      foo_details( T* in ):data(in) {}
      foo_details( foo_details const& ) = delete;
      foo_details& operator=( foo_details const& ) = delete;
      foo_details():data(nullptr) { header.deleter=nullptr; }
    };
    

    然后分配一个foo_detailsfoo_obj 存储一个fooreinterpret_cast 到一个foo_impl(在标准布局条款下有效),并存储到foo_obj

    然后deleter 会将foo_implreinterpret_cast 转换为foo_details&lt;foo&gt;delete

    要访问数据,您必须弄清楚它是什么类型(您可以在foo_impl 中粘贴额外的类型信息,例如整数或其他),然后将reinterpret_cast 粘贴到适当的foo_details&lt;?&gt; 和访问其中的data

    意识到您需要能够以某种方式提取不透明指针的类型信息才能使用它:考虑使用您在那里使用的任何机制来确定如何删除它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-29
      • 2018-10-02
      • 2019-03-14
      • 2018-05-22
      • 1970-01-01
      • 2010-12-01
      • 2013-09-09
      • 1970-01-01
      相关资源
      最近更新 更多