如何正确删除作为void指针的对象?

时间:2014-07-30 12:50:47

标签: c++ c pointers void-pointers

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

foo

我定义了一个struct C_foo { void *foo_obj; }; C接口函数,用于分配alloc()类型的对象:

C_foo

我现在要做的是创建一个struct C_foo* alloc(/* input */) { struct C_foo *out = new struct C_foo; out->foo_obj = new foo(/* input */); return out; }; C接口函数,该函数将正确释放先前使用dealloc()分配的C_foo类型的对象:

alloc()

我知道明确删除void dealloc(struct C_foo *obj) { /* ??? */ } 指针(即void*)会导致未定义的行为(§5.3.5/ 1 [expr.delete] ):

  

delete obj->foo_obj; - 表达式的结果类型为delete [81]。

     

[81]这意味着无法使用类型为 void 的指针删除对象,因为void*不是对象类型

问题:

我将如何正确释放void对象?

5 个答案:

答案 0 :(得分:11)

如果你知道(肯定)它指向什么类型,那么施放:

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

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

答案 1 :(得分:4)

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

请注意,如果您的所有类型共享一个公共基础,您只需为基础提供虚拟析构函数static_cast void*指向基础的指针,然后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*成员来维持不透明度。

答案 2 :(得分:3)

投射到正确的类型:

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

答案 3 :(得分:1)

使用虚拟析构函数创建公共基类,并使用它而不是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并转回{ {1}}当您需要删除它时。

答案 4 :(得分:1)

我将假设您始终将相同类型的实例放入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*中添加多种不同类型的类型,我首先建议将纯虚拟接口类与虚拟析构函数放在一起,基本上遵循上述步骤。


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

再次,更喜欢我的上述方法,但如果它不起作用,我们就有这个。

有几种方法可以存储删除功能:

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_details以保留foo_obj fooreinterpret_castfoo_impl(在标准布局条款下有效),并存储进入foo_obj

然后deleter会将foo_implreinterpret_cast带到foo_details<foo>delete

要访问数据,您必须弄清楚它是什么类型(您可以在foo_impl中添加额外类型信息,如整数或其他),然后reinterpret_cast到相应的foo_details<?>并访问其中的data

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