Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Virtual functions and cast to void and back

Currently I am working with a legacy c++ code base. In this codebase pointer to objects are converted to void-pointers and then stored in a c-library. Consider the following code:

class interface {
public:
  virtual void foo() {
    std::cout << "Interface" << std::endl;}
  virtual ~interface(){};
};

class debug_interface: public interface {
public:
  virtual void foo() {
   std::cout << "Debug Interface" << std::endl;}
};

The objects interface and debug_interface are allocated on the heap and the address is stored to a void pointer. At some point the pointers are retrieved and then casted back to the base-class interface. Then the virtual function call is invoked. See

int main(int argc, char *argv[]){

    void *handle = reinterpret_cast<void*>(new interface());
    void *debug_handle = reinterpret_cast<void*>(new debug_interface());

   //void *handle = new interface();
   //void *debug_handle = new debug_interface();

   interface *foo1 = reinterpret_cast<interface*>(handle);
   interface *foo2 = reinterpret_cast<interface*>(debug_handle);

   //interface *foo1 = static_cast<interface*>(handle);
   //interface *foo2 = static_cast<interface*>(debug_handle);

   foo1->foo();
   foo2->foo();

   return 0;
}

First of all I don't understand, why reinterpret_cast is used. As far as I know, pointer-to-objects can be implicitly converted to void*. Furthermore, to make this cast explicit, a static_cast would be enough, right? But the more important question: Is it really save to cast the pointer debug_handle to interface* ( not to debug_interface*) and invoke the virtual call? According to the c++-standard, (5.2.10) this is undefined behavior:

A pointer to an object can be explicitly converted to a pointer to a different object type.69 When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast(static_cast(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified.

The conversion from handle to foo1 should be ok, but I can again use a static_cast?

Edit My example source code was wrong. debug_interface is a derived class of interface.

like image 238
IcePic Avatar asked Aug 21 '15 07:08

IcePic


People also ask

Does a virtual function have to be void?

Yes virtual function used for polymorphism can return any type, instead of being a void.

What does void * func () mean?

Void functions, also called nonvalue-returning functions, are used just like value-returning functions except void return types do not return a value when the function is executed. The void function accomplishes its task and then returns control to the caller. The void function call is a stand-alone statement.

What does casting to void do in C?

Casting to void is used to suppress compiler warnings. The Standard says in §5.2. 9/4 says, Any expression can be explicitly converted to type “cv void.” The expression value is discarded. Follow this answer to receive notifications.

What is function overriding and virtual function?

The virtual keyword can be used when declaring overriding functions in a derived class, but it is unnecessary; overrides of virtual functions are always virtual. Virtual functions in a base class must be defined unless they are declared using the pure-specifier.


1 Answers

Disclaimer: This first part was written when the two interfaces were not related by inheritance.

The undefined behaviour actually happens here:

   foo2->foo();

Here, you are using the interface API on a pointer to an object that is not implementing this API. The fact that both interface and debug_interface happen to implement the foo() member as their first method does not change anything: those classes are not related by inheritance, so they are not compatible.

The extract you are citing treats about cases where the conversion itself is allowed. In your case, my understanding is that you can actually convert a pointer to debug_interface to a pointer to interface: yet, the only safe thing you can now do with you pointer to interface is to convert it back to a debug_interface pointer: using it to access interface members is unsafe.


EDIT: If debug_interface publicly derives from interface, this is a different problem.

In this case, it would be totally safe to cast from debug_interface* to interface*: the derived-to-base conversion can even be applied implicitly by the compiler. Yet to be safe, this cast must be done directly, through either:

  • a static_cast
  • a dynamic_cast (which would introduce runtime check, being overkill for an upcast).

Doing it through two reinterpret_cast is an undefined behaviour: it is likely to work for single inheritance (on some compilers), but it is absolutely not guaranteed by the standard.
Doing it through two static_cast would also be an undefined behaviour. The standard guarantees that (emphasis mine):

A value of type pointer to object converted to “pointer to cv void” and back to the original pointer type will have its original value.

In your example, you are not converting back to the original pointer, but to another pointer type: the standard is not giving you any guarantee about the value you will get.

Possible solution

Knowing that:

  1. It is safe to directly convert from debug_interface to interface
  2. It is safe to convert from a pointer to object type to void * then back to pointer to the same object type.

You could assemble that to get a standard guaranteed solution:

// Safe, see point #1.
// The new expression returns a debug_interface* and static_cast applies a derived-to-base conversion.
interface *debug_handle_interim = static_cast<interface*>(new debug_interface());

// Convert a interface* to void* then back to interface*, see #2
void *type_erased = static_cast<void*>(debug_handle_interim);
interface *debug_handle_back = static_cast<interface*>(type_erased);
like image 79
Ad N Avatar answered Oct 08 '22 19:10

Ad N