Search code examples
c++messagingvoid-pointers

How "safe" is it to use void pointers in designing a messaging system in C++?


I am trying to implement a generic messaging system in my C++ application.

The program has been divided into different systems that handle different things like rendering, image loading, etc. I want them to be able to push a message into a message stack, with some type of a data to transfer with it.

For implementing this I have made an API that allows this:

MessageStack.add(System * sender, 
                 System * receiver, 
                 MessageCodeEnum message_type, 
                 void * data);

The variable message_type is an enum that defines all message commands there are and the void * data is supposed to be the data attached to the message. These messages are arranged in a stack and are evaluated at the end of each frame.

Suppose the renderer messages the fileLoader to load an image. The renderer will add a message to tell fileLoader to load the image and the message will have a void * inside it that is actually a const char * that points to a C string containing the path of the image file.

Inside the switch case block in the function that handles this message, I have type-casted the message data that was a void * to a const char * by using the plain old way:

const char * temp = (const char *) data;

Now this brings me to this question. If during any message transaction, the program knows what kind of a data is being transmitted, which is why I feel confident while type-casting a void *, does the usage of void * really be an unsafe method to achieve such functionality? Are there any better ways to achieve such a level of loosened type checking?


Solution

  • Using void pointers is inherently unsafe, in the sense that it is VERY EASY for a programmer to misuse them, and your compiler can rarely diagnose misuse. Programmers also tend to be over-confident in their ability to write good code, which doesn't work well when using void pointers since they are so easy to misuse.

    If you make a mistake, and use the pointer incorrectly (e.g. casting it to X *, where it doesn't actually point at an X, and then accessing members of X via that pointer) then behaviour of your code is undefined.

    Even worse, programmers tend to think that "I can find where I've made a mistake with a pointer because the program will crash". The sad thing is: more often than not, that is untrue. A lot of pointer errors — and particularly misuses of void pointers — sail through compiling and testing without errors. Either because they are missed in testing (the programmer didn't think THAT could happen, so didn't test for it) or because the result of the error is not a crash (e.g. poisoning data in your program, such as overwriting innocent variables, is rarely detected at the point it occurs). With misuse of void pointers, the misuse can be so subtle that the error remains undetected for quite a long time, the symptoms can affect unrelated code, so the cause is VERY hard to find.

    You can mitigate the effects of that lack of safety through careful design (design it in a way that makes it harder to misuse the pointers), careful coding (to minimise errors in translating that design into code), and exhaustive testing (a comprehensive testing of your code can help increase your confidence that your code does not misuse those pointers). In short, it takes effort and time on your part. However, miss one thing, and you are in trouble. Miss a particular corner case during design, and your program will have a design flaw that results in pointer misuse. Introduce a typo that causes a pointer to be misused, and you're in trouble. Don't exercise that design flaw during testing, and you will have a bug waiting to be found - in worst case, by the user of your program.

    Generally speaking, it is better to use a technique that doesn't involve using void pointers. Specify an interface for your data and message, and check that. Provide your data in a typesafe manner. Use a polymorphic base class with a carefully designed set of operations. The list of options goes on.

    If you exhaust all other design possibilities, then - yeah - consider using void pointers. But accept they are dangerous, so a lot of effort on YOUR part will be needed to ensure your messaging system is robust, and minimise chances for other people (developers or end users) to break it.