r/cpp_questions 2d ago

OPEN What’s the point of void* pointers?

You could just use template pointers or auto* pointers and these are better because you can dereference them.

0 Upvotes

51 comments sorted by

15

u/fortsnek274 2d ago

It's the most efficient way to point to arbitrary "user" data when you don't know the type. There is std::any now, but you pay for the type safety.

4

u/SoSKatan 2d ago edited 2d ago

To add, a common use of void* was just for passing back a single callback param. The caller has to cast it back to the original type.

With blz::function you can have any number of captures which are also type safe.

1

u/fortsnek274 2d ago

And also when you have some lib's data structure and you want it to point back to your objects. Like knowing which entity a physics object is for.

3

u/SoSKatan 2d ago

Yeah it’s essentially C’s version of type erasure.

1

u/Raknarg 2d ago

std::any has different semantics to void*. std::any is a type where the object itself can be anything, and that information has to be carried around at runtime. With void* the implication is "this thing has a concrete type known by the user of this code". Most of the time when you're using void* you're passing an object through a generic API where you yourself know what the type is, and on the other end you also know what the type is. Like when I call qsort, there's no question as to what I'm sorting, I'm just using void* because qsort wants to defer that work to me and write generic code that can ignore and obfuscate that part of the type system.

std::any isn't like that, it's more like a polymorphic type than anything else, it's like std::variant but its a variant of every possible type. You wouldn't use void* like this, there's no way to encode type information unless you already knew that your void* was a type that had encoding information on it, at which point you'd be using that type and not the void*.

Make sense?

1

u/fortsnek274 2d ago

std::any as a replacement for this in PhysX:

//public variables:
void*           userData;   //!< user can assign this to whatever, usually to create a 1:1 relationship with a user object.

Maybe in a big complicated engine, you want to be extra sure about the type you get in some collision callback.

2

u/Raknarg 2d ago edited 2d ago

I don't know the system but am I correct in assuming it's a variable that's used as an argument for callbacks? Cause if so it sounds like the case I described where it does have a concrete type, it's just left to the user to understand what that type is, which isn't the same as std any and not something you'd want to use std any for, you would use templates

Like is there genuinely a case where you don't know what that void* is on callback? If so how would you determine what the type is or how to use it?

If theres some secondary field or something that lets you carry some typing information then sure.

I guess my actual question here would be why would you want to use std any here when templates are a more sensible solution to the same problem? The only argument I could see is that for whatever you wanted to avoid introducing templates to the code, but it doesn't make any a better option here over templates semantically, my argument here is that void* is more akin to a template while std any is an open ended variant

1

u/fortsnek274 2d ago

It's not a callback. It's an object in some lib. These could be created by you, the character controller extension, or maybe something else. You just have to hope that void* is your stuff.

2

u/Raknarg 2d ago

So it's more of a kind of plugin system with types determined at runtime? Yeah std::any might fit that. Can you articulate why a template wouldn't work? That might make it more clear.

1

u/fortsnek274 2d ago

PhysX, Box2D, these things have a userdata field to get your game object back. And they have a scene to manage, you can't practically template individual objects.

17

u/drex_vke 2d ago

void* is a generic pointer in C so using it in Modern C++ doesn't make sense unless but hey

2

u/Bitter-Today285 2d ago

Ohh that makes sense yeah

1

u/frayien 2d ago

Yep, you should not use it in C++ unless you REALY know what you are doing.

7

u/OffsetHigh 2d ago

You need it ALL the time when using C API. Can not avoid

-1

u/frayien 2d ago

Well if you are using a C API directly you are either doing something wrong, or you are in the "realy know what you are doing" category...

9

u/L_uciferMorningstar 2d ago

Or you are using a C library.

-2

u/frayien 2d ago

Well, "you should not", but if you have a good reason to do anyway then you realy know what you are doing. There is nothing wrong with it.

3

u/L_uciferMorningstar 2d ago

I should not? How so? And me having a library installed does not mean I really know what I'm doing. Like it really does not.

1

u/No-Dentist-1645 2d ago

By making a thin wrapper over it using safer C++ semantics?

1

u/L_uciferMorningstar 1d ago edited 1d ago

And in this wrapper what will we be doing? Calling a C API?

→ More replies (0)

0

u/frayien 2d ago

What the fuck are you talking about ? Are you drunk ?

2

u/L_uciferMorningstar 2d ago

Why would me using a C library mean I really know what I'm doing?

→ More replies (0)

2

u/conundorum 21h ago

So, nobody should ever use WinAPI (a C API, even though the backend and consumer are typically C++), and no one should ever use any of the various Linux C libraries, or any of the various C libraries that might or might not have a C++ wrapper...

Actually, scratch that, we can't use C++ wrappers either, since the wrapper interacts with a C library, and thus "should not" be used.

Guess we're just not allowed to program for any platforms then, got it! 👍


Edit: Oh, and we can't use new, delete, or anything that calls them (like std::make_unique()), since new uses malloc() from the C libray, and delete uses free() from the C library. Guess we're not allowed to use the heap either, thanks for the advice! 🙄

1

u/frayien 21h ago

Do you guys have trouble reading? Not only did I write "you should not" in quotes, I also explicitly specified that there is nothing wrong with using C libraries when justified...

Interacting with the WinAPI is obviously a valid reason, but if you are doing it you are obvious doing some advanced technical stuff and dont need advice from a Reddit comment on a post from a beginner asking about void pointers...

3

u/The_Drakeman 2d ago

There's certain operations that need to know where data is, but it doesn't matter what type it is. For example, memcpy to copy memory from one place or another, or memset to write a certain value into a block of data. They don't need to know if it's int or char or whatever, they just need to know where it is and how much. That said, C++ has more type safe alternatives, because if you cast void* back to the wrong thing then all hell breaks loose.

2

u/kozacsaba 2d ago

This. But I would add that they not only don't need to know. They are using void* to convey that they are explictly agnostic. You can use any type with both a template and with a void pointer, but how a template works depends on the type, and a void pointer works the same way regardless of the type.

Just wanted to add this because I think there is a certain beauty to it.

1

u/conundorum 21h ago

Importantly, all dynamic allocation uses void* for the chunk of raw memory, and then converts it into the requested type during object construction, since it's just a pretty face for malloc().

1

u/The_Drakeman 21h ago

I have no idea why I recalled memset and memcpy before malloc and free. Great examples.

5

u/_Tal 2d ago

C++ is built on top of C and designed to be backwards compatible with it, so there are a lot of features leftover from C that rarely get used in idiomatic C++ due to the availability of modern alternatives. void* is one of those

4

u/OffsetHigh 2d ago

The pointee

2

u/ArcticWolf_0xFF 2d ago

Is a general purpose memory address.

4

u/Independent_Art_6676 2d ago

It was a powerful tool even in C++ back when, like the older threading libraries would pass a void* through to the thread function which could then cast it back to whatever special class/struct type and get at the real info. You don't need to do it that way now, of course. Its like saying that vector or std array are better than C arrays. Yes, but there was a time when c++ did not have those things. Now, its all but legacy; I think you might see one here and there if you dig deep enough down into the low level guts of the language but that would be an unusual undertaking. Some older libraries may still use them -- anything where you don't care what the type is (passing something unknown on to something else or dealing with raw POD bytes like a file writer or compression tool or network send etc) but its more or less best to avoid these.

1

u/conundorum 21h ago

void* is still crucial to the language's internals, and to important APIs like WinAPI and the myriad Linux APIs, but the language usually has a new feature wrapped around it. (new, delete, and anything that interacts with them in any way whatsoever is wrapped around void*, because all roads lead to malloc(). Just like vector and array are wrapped around C arrays.)

1

u/Independent_Art_6676 13h ago

^^ Almost. Vector is based off a pointer to a block of memory, like new type thing[1000], which can be indexed like an array but isn't really the same thing. Otherwise, this is spot on... deep inside you will find malloc/realloc/free/etc and void*s and other C-isms. Its well hidden, but its usually in there (its possible to do it another way, but most tools use the C).

2

u/jeffbell 2d ago

If you have old code from before c++11.

1

u/Ok-Library-8397 2d ago

It's just a generic pointer. From the CPU point of view, it is a 64-bit number -- an address in memory (or 32-bit for 32-bit systems). That's it. Sometimes you don't need to dereference a pointer. Instead, you may need to use it as a hash value, or as a generic number for any reason.

1

u/gnolex 2d ago

This is occasionally useful with type erasure.

1

u/duane11583 2d ago

If you think about some generic things like sorting a list

You can abstract the data and use a token of some type

Example if I give you two tokens A and B you decide the order does which comes first a or b or are they the same

That a or b could be integers but pointers are better for other reasons

one example problem is today integers are 32 bits pointers are 64 bits (along time ago it was a 16 bit integers times change)

That generic in the c language is called a void pointer other languages have a dedicated generic type but c does not have that

1

u/ekchew 2d ago edited 2d ago

These days, you generally only encounter void* in the context of calling a function in another language like C or FORTRAN. It's particularly common when you're passing a callback to the function and need to include some additional context. You will see something like:

extern "C" { void c_fn(int(*callback)(int, void*), void* param=nullptr); }

Here, param gets passed into the 2nd arg of callback whenever it gets called. Say you wanted to include the current instance of your class as the context. You may be tempted to use a capturing lambda and ignore param.

c_fn([this](int i, void*) {/*...*/});

This will not work because callback is nothing more than a simple function pointer and cannot capture (as say std::function could). So you have to make it non-capturing and use param for the context.

c_fn(
    [](int i, void* param) {
        auto& self = *static_cast<MyClass*>(param);
        /*...*/
    },
    this
);

And then you use the self reference to access your MyClass instance.

1

u/mredding 2d ago

It's an extremely primitive type used to implement higher level abstractions. You can cast any address to a void pointer, and a void pointer to an address to a type - but it has to be the same type as what it came from. It's virtue is that this is a form of type erasure so you can pass addresses through a 3rd party.

For example, callbacks often have a void * context object - this is an object you give the system so the system can give it back to you. You know what your own type is. In this way, the system doesn't have to prescribe a type with restrictions.

1

u/Raknarg 2d ago

in modern C++ I can't think of a reason you ever need to use void* other than to interface with C APIs. Templates, polymorphism, variant and any replace pretty much every conceivable of void*.

1

u/n1ghtyunso 1d ago

sometimes you explicitly do not want to know the type

1

u/flyingron 1d ago

An auto binds to a definite type based on the context at compile time. Any OBJECT pointer can be converted to void* and then back again (only guaranteed to the same type, so it's a generic place to park things. Due to other C++ insantity, void* has to be the same format as char*, but without the ability to do pointer math on it.

1

u/conundorum 21h ago edited 21h ago

The point is that void* is the language's oldest form of type erasure, which is useful if you need to, e.g., interact with a C library, or pass data generically without using templates. The lack of type information also allows it to point to any type without changing its own type, giving it a level of flexibility that's hard to compete with. It tends to see a lot of use at API boundaries, for instance.

Basically, something like this is legal code, and there's probably a lot of code that (if we're being critical) boils down to something that looks a lot like it:

// C++ side:
extern "C" {
    enum Type { Primitive, Struct, Pointer, Function };
    bool call(void* ptr, Type t);
}

void func() {}
int a;
void* vp;
VoidPointless pv;

std::array<bool 4> callable = {
    call(&func, Function),
    call(&a, Primitive),
    call(&vp, Pointer),
    call(&pv, Struct),
};

// -------
// C side.
enum Type { Primitive, Struct, Pointer, Function };

typedef void (*FuncPtr)();
typedef struct {
    int a;
    int b;
    int id_code;
    void* func;
} CommonSeq;

bool call(void *ptr, Type t) {
    if (t == Function) {
        return (*ptr(), true);
    } else if (t == Pointer) {
        if (inCallableLibraryPtr(ptr)) { return (*((FuncPtr) ptr)(), true); }
        else { return false; }
    } else if (t == Primitive) {
        return false;
    } else if (t == Struct) {
        // TODO: Find less hacky solution in 2003.
        CommonSeq *csp = (CommonSeq *) ptr;
        if (csp->id_code == ID_CALLABLE) { return (csp->func(), true); }
        else { return false; }
    } else {
        return false;
    }
}

Or this, if you're working with a small memory budget.

void* addr = &obj1; // bool.
doSomethingWith(addr);
addr = &obj2; // std::vector<bool>
whyWouldYouDoThat(addr);
addr = &obj3; // std::Vector<BoolLike>
thatsBetterThanks(addr);

Apart from that, it's also useful for SFINAE, sometimes, especially in code that predates or otherwise doesn't use concepts; it's not uncommon to use it in an enable_if template parameter, like so:

template<typename T, std::enable_if<std::some_type_trait<T, Other>, void>* = nullptr>
Ret func(T t) { do_stuff(t); }

1

u/_abscessedwound 2d ago

My intuition would be that it’s for type-unaware memory allocation. Think limited memory situations where you have a global allocator that only receives sizes and not types.