简体   繁体   中英

How to properly delete an object that is a void pointer?

I'm trying to interface a C++ class (eg, class foo ) to C. What I have done so far is to define a C structure that holds an opaque pointer member variable (ie, void* ), that points to the associated C++ foo object.

struct C_foo {
  void *foo_obj;
};

I defined an alloc() C interface function that allocates objects of type C_foo :

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

  return out;
};

What I want to do now, is to create a dealloc() C interface function that will properly deallocate objects of type C_foo previously allocated with alloc() shown above:

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

I know that explicitly deleting the void* pointer (ie, delete obj->foo_obj; ) would results in undefined behaviour ( § 5.3.5/1 [expr.delete] ):

The delete -expression's result has type void [81].

[81] This implies that an object cannot be deleted using a pointer of type void* because void is not an object type .

Question:

How I'm going to properly deallocate a struct C_foo object?

If you know (for sure) what type it points to, then cast:

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

If you've lost track of the type, then you'll need to redesign.

If you need to pass around opaque handles because of some C API, and your objects have totally disparate types, you can use an approach like the one outlined below.

Note that if your types all share a common base , you can just provide a virtual destructor for the base, static_cast the void* to a pointer to base, then delete that. This is a much more common approach than the one I outline below.

The handle struct

This will need to hold a pointer to the object yuo allocated, and a pointer to something that encodes the type (so that you can delete it):

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

The C++ Implementation Detail

You'll have some classes; these are the things you want to pass around handles to instances of...

class Foo;
class Bar;
// etc

You'll also need a base class for your deleter:

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

... and a class template to produce derived classes that know the relevant type:

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

Creating objects

For each type you want to provide handles to, you'll need a function to create a handle:

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;
}

Destroying objects

You'll need a destroy function:

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
}

Notes

The struct could hold a deleter_base* deleter_info as opposed to void* deleter_info . This is really a matter of taste, and whether you want a struct deleter_info; in your C API. Storing it in a void* hides the implementation details, making it truly opaque.

To be able to use the handle meaningfully, you'll also need to encode some other information to be able to retrieve something useful from the void* handle member. Typically, variant types do this with an enum member. Alternatively, you could hope your users are smart enough to only pass their handles back to a function that expects a handle of the correct type. You could have different handle struct types ( struct HandleFoo; , struct HandleBar; , ...) to enforce this, and still use void* members internally to maintain opacity.

转换为正确的类型:

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

Create a common base class with a virtual destructor, and use that instead of void*.

You could do something like the following.

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) {}
};

You can use dynamic_cast to convert GenericBase* to a concrete type, in order to benefit from safety-checking.

I just noticed you want to use this with C. Obviously you can't pass a GenericBase* to C. But you can pass it to C as a void* and cast back to GenericBase* when you need to delete it.

I will assume that you always put an instance of the same type into that void* .

In which case, pImpl time:

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++
};

now most of your problem goes away. C treats foo_obj as an opaque pointer.

In C++ we include another header file (sample fields):

// 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;
}

and you win.

Note that a struct is just a name for a class in C++ with default public instead of default private .

I changed the name from foo to foo_impl , and created some sample data in it.

If you could put more than one different kind of type into your void* , I would first advise putting a pure-virtual interface class with a virtual destructor, and basically following the above steps.


Now, there are cases where you actually want to store more than one distinct, unrelated type in your opaque pointer. These are not that common. But in those cases, we will want to store a destroy function.

Again, prefer my above approach, but if it doesn't work, we have this one.

There are a few ways to store the deleter function:

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

another approach is:

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; }
};

then allocate a foo_details to stick into the foo_obj storing a foo , reinterpret_cast to a foo_impl (valid under standard layout clauses), and store into foo_obj .

The deleter would then take a foo_impl , reinterpret_cast to a foo_details<foo> and delete .

To access the data, you'd have to figure out what type it is (you can stick extra type information in the foo_impl , like an integer or whatever), then reinterpret_cast to the appropriate foo_details<?> and access the data in it.

Realize that you'll need to be able to extract the type information of your opaque pointer somehow in order to use it: consider using whatever mechanism you use there to also determine how to delete it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM