Is void* as key a bad idea?

D

DeMarcus

Hi,

Would it for any reason be a bad idea to have void* as key in a std::set
to test if a variable has been registered already?

std::set<void*> regSet;

template<typename T>
void add( T* i )
{
if( regSet.insert( i ).second == false )
std::cout << "ERROR!" << std::endl;

/* ... */
}

Or do you consider the example above fully ok?

Thanks,
Daniel
 
K

Kaz Kylheku

Hi,

Would it for any reason be a bad idea to have void* as key in a std::set
to test if a variable has been registered already?

Converting an object pointer to void * is valid, and comparing two
void * pointers for exact equality is also valid. If such a comparison
yields true, it indicates that the pointers are aimed at the same
object.

The drawback (if it may even be considered one) is that you can't
use this mechanism to distinguish an object from a subobject
which is aligned to its base address.
std::set<void*> regSet;

template<typename T>
void add( T* i )
{
if( regSet.insert( i ).second == false )
std::cout << "ERROR!" << std::endl;

/* ... */
}

So here, if you have an object struct s { int i; } so;
you can't add(&so) and add(&so.i): the pointers are to the same address.

If that's not a requirement, you're fine.

Also watch out for pointers going invalid, while remaining in the set,
e.g:

{ { int x; add(&x); } { int y; add(&y); } }

That of course is a separate issue from whether to use void *.
 
A

Alf P. Steinbach

* DeMarcus:
Hi,

Would it for any reason be a bad idea to have void* as key in a std::set
to test if a variable has been registered already?

std::set<void*> regSet;

template<typename T>
void add( T* i )
{
if( regSet.insert( i ).second == false )
std::cout << "ERROR!" << std::endl;

Throw an exception. For example, think about using this in a GUI program.
/* ... */
}

Or do you consider the example above fully ok?

The above is generally not OK, even with corrected failure handling.

There are two main cases.

One, you know the type of pointers in the set. Or at least a common interface
type. Then declare the set with that type (not done above).

Or two, the set will contain pointers to objects of different types. Generally
this is only meaningful for class types. Then void* might be suitable but you
need to be sure to store pointers to most derived objects (not done above).

You can use dynamic_cast<void*>(p) to obtain most derived object pointers if
every type is polymorphic. Otherwise you risk not detecting that a pointer to an
object is already in the set. Pointers to the same object but accessed via
different static types may /not/ necessarily yield the same void* pointers.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* Leigh Johnston:
I disagree, generally it is OK, the two cases you mention below
notwithstanding.


There is nothing fundamentally wrong with using void* in a container
however for the particular case of ensuring variable identity there are
corner cases yes, the main one being two "variables" occupying the same
address (class object and its first member variable).

I'd tend to agree with that.

Only use dynamic_cast sparingly and in this case it is only really
required if multiple inheritance is being used.

No, you don't need multiple inheritance to get into problems.

All that's needed is inheritance.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* Leigh Johnston:
Example? And don't say virtual inheritance.

Please quote enough of the article you're responding to to establish the
necessary context for your response. Not all readers have easy access to the
thread history.

Anyway, you're asking for and doubting the existence of this problem:

#include <assert.h>

struct A
{
int blah;
};

struct B: A
{
virtual ~B() {}
int doh;
};

int main()
{
B* p1 = new B;
A* p2 = p1;
void* pv1 = p1;
void* pv2 = p2;

assert( pv1 == pv2 ); // Uh oh, not guaranteed.
}

To some C++ programmers it comes as a surprise.

Note that the introduction of a virtual destructor in the derived class is not
necessary in order to have this problem, except that with that it's easier to
convince folks since then the assertion fails with two popular compilers.


Cheers & hth.,

- Alf
 
J

James Kanze

There is nothing fundamentally wrong with using void* in a
container however for the particular case of ensuring variable
identity there are corner cases yes, the main one being two
"variables" occupying the same address (class object and its
first member variable).

The other potential problem is that two pointers to the same
object might not compare equal, if they're obtained from
different base classes.

I don't know exactly what the original poster is trying to
accomplish, so it's difficult to be more precise. If he can
use a common base class, and a pointer to that, that's generally
a better solution than using void*. If he can't, and is
interested in registering complete objects, he has to somehow
ensure that it is always a pointer to the most derived class
that gets converted into void*.
Only use dynamic_cast sparingly and in this case it is only
really required if multiple inheritance is being used.

Or not---formally, it depends on the implementation.
Practically, of course, regardless of the implementation, some
day, someone will use multiple inheritance, and you're screwed.
 
A

Alf P. Steinbach

* Leigh Johnston:
Except dynamic_cast<void*>(p2) will not work as A is not polymorphic. :)

Yes. You have the same problem with polymorphic A. Except that then it can be
harder to find a compiler and set of definitions where the problem manifests.

Cheers & hth.,

- Alf
 
D

DeMarcus

Alf said:
* Leigh Johnston:

Please quote enough of the article you're responding to to establish the
necessary context for your response. Not all readers have easy access to
the thread history.

Anyway, you're asking for and doubting the existence of this problem:

#include <assert.h>

struct A
{
int blah;
};

struct B: A
{
virtual ~B() {}
int doh;
};

int main()
{
B* p1 = new B;
A* p2 = p1;
void* pv1 = p1;
void* pv2 = p2;

assert( pv1 == pv2 ); // Uh oh, not guaranteed.
}

To some C++ programmers it comes as a surprise.

I had no idea this was the case! I've spent a while trying to find more
to read about this, both in Stroustrup's book and on C++-faq-lite
without luck.

I would appreciate if you could direct me to more information.

Thanks!
 
D

DeMarcus

James said:
The other potential problem is that two pointers to the same
object might not compare equal, if they're obtained from
different base classes.

I don't know exactly what the original poster is trying to
accomplish, so it's difficult to be more precise. If he can
use a common base class, and a pointer to that, that's generally
a better solution than using void*. If he can't, and is
interested in registering complete objects, he has to somehow
ensure that it is always a pointer to the most derived class
that gets converted into void*.

I have a kind of factory that creates objects of any type. It is built
on a template function and it uses variables to initialize the objects.
However, one init variable can only be used once, that is what I'm
trying to prevent. It is similar to this.

class Factory
{
public:

template<typename T>
T* createObject( T* init )
{
if( regSet_.insert( init ).second == false )
throw Error();

return new T( init );
}

private:
std::set<void*> regSet_;
};

I can't make Factory a template since I need to store more common data.
Is this a hopeless situation?

Or not---formally, it depends on the implementation.
Practically, of course, regardless of the implementation, some
day, someone will use multiple inheritance, and you're screwed.

This doesn't look good. I mean policies by Alexandrescu are built on
multiple inheritance.


 
D

DeMarcus

Alf said:
* Leigh Johnston:

Please quote enough of the article you're responding to to establish the
necessary context for your response. Not all readers have easy access to
the thread history.

Anyway, you're asking for and doubting the existence of this problem:

#include <assert.h>

struct A
{
int blah;
};

struct B: A
{
virtual ~B() {}
int doh;
};

int main()
{
B* p1 = new B;
A* p2 = p1;
void* pv1 = p1;
void* pv2 = p2;

assert( pv1 == pv2 ); // Uh oh, not guaranteed.
}

To some C++ programmers it comes as a surprise.

Note that the introduction of a virtual destructor in the derived class
is not necessary in order to have this problem, except that with that
it's easier to convince folks since then the assertion fails with two
popular compilers.

How can I solve this to always be safe? Would a base class solve everything?

struct A
{
int a;
};

struct B : A
{
virtual ~B() {}
int b;
}

struct C : A
{
virtual ~C() {}
int c;
}

struct D : B, C
{
virtual ~D() {}
int d;
}

int main()
{
B* b = new B;
A* a = b;

assert( a == b );

C* c = new C;
a = c;

assert( a == c );

D* d = new D;
a = d;

assert( a == d );

}
 
A

Alf P. Steinbach

* DeMarcus:
I had no idea this was the case! I've spent a while trying to find more
to read about this, both in Stroustrup's book and on C++-faq-lite
without luck.

Since the problem is the absence of a guarantee it's pretty difficult to find it
-- looking for what's not there...

And the guarantee that /is/ given is one of the most unknown in C++ programming
because it's designed only for support of C code emulation of inheritance and
because it's flagrantly in contradiction with some earlier general discussion of
reinterpret_cast, namely §9.2/17, "A pointer to a POD struct, suitably converted
using reinterpret_cast, points to its initial member (or if that member is a
bit-field, then to the unit in which it resides) and vice versa".

As soon as you have inheritance you're leaving POD-land.

I would appreciate if you could direct me to more information.

Except for the above I don't know about sources discussing this, sorry.

To some extent dynamic_cast<void*> is designed as a kind of solution, §5.2.7/7.


Cheers & hth.,

- Alf
 
A

Alf P. Steinbach

* DeMarcus:
How can I solve this to always be safe? Would a base class solve
everything?

struct A
{
int a;
};

struct B : A
{
virtual ~B() {}
int b;
}

struct C : A
{
virtual ~C() {}
int c;
}

struct D : B, C
{
virtual ~D() {}
int d;
}

int main()
{
B* b = new B;
A* a = b;

assert( a == b );

C* c = new C;
a = c;

assert( a == c );

D* d = new D;
a = d;

assert( a == d );

}

Yes, it's one solution. Here the pointers are all converted up to A*, and
adjusted appropriately (which is one important difference between static_cast
and reinterpret_cast; the latter is not guaranteed to adjust). Another solution
is as mentioned to have all classes polymorphic and use dynamic_cast<void*> to
obtain pointer to most derived object.


Cheers & hth.,

- Alf
 
D

DeMarcus

Alf said:
* DeMarcus:

Yes, it's one solution. Here the pointers are all converted up to A*,
and adjusted appropriately (which is one important difference between
static_cast and reinterpret_cast; the latter is not guaranteed to
adjust). Another solution is as mentioned to have all classes
polymorphic and use dynamic_cast<void*> to obtain pointer to most
derived object.


Cheers & hth.,

- Alf

Thanks for the insight!! Before I just went on with my void*, but after
a while I felt that there might be a small possibility of problems using
void*, hence I posted this thread. At least my intuition works sometimes. ;)

You brought light to my problem. Thanks!
 
K

Kaz Kylheku

How can I solve this to always be safe? Would a base class solve everything?

Using virtual inheritance and a sprinkling of templates.

This is a problem of reducing an object to a unique ID.

A C++ class object can be observed through various views: namely through
a reference or pointer to any of its base classes.

There is no general way, other than being careful, to write an object
such that a call to a virtual function REF.func() will always
call the same function func. In a C++ class lattice, if two classes B1
and B2 appear on independent branches, not connected by inheritance
(e.g. siblings), and both define a virtual function f with the same
argument signature, then you get a different f if you call through a B1
reference or B2 reference to the object. These are distinct functions
and do not override each other. The only way that the same f is obtained
is if the function f is overridden at another node in the class lattice,
which inherits from B1 and B2.

So we cannot simply use a ``virtual void *id()'' function
to solve this problem in a completely fool-proof way.

However, we can use a simple nonvirtual function placed into
a base class, and ensure that we always use virtual inheritance
for that class:

class object_with_id {
public:
void *id() { return this; }
};

class myclass : virtual public object_with_id { ... };

So now even if two class writers independently inherit from
object_with_id (using virtual inheritance), and then, in turn, their
classes are combined togheter with inheritance, the resulting object
will have one copy of object_with_id, and thus just a single
object_with_id::id function which returns just one possible pointer.

Now how you might use this would be to to write a wrapper
template function called id:

#include <cstdlib> // for abort

// in general, we don't know whether a pointer serves as an ID
// so to be safe, we abort.
// (We could insert some kind of compile-time constraint
// violation here which is only triggered at template expansion
// time, to get a compile-time check).

template <typename T>
void *id(T *ptr)
{
abort();
}

// For objects which inherit from our object_id base,
// we /can/ compute the id, with this specialization:

template <>
void *id(object_with_id *ptr)
{
return ptr->id();
}

// Objects of the basic type int have a straightforward id:

template <>
void *id(int *ptr)
{
return ptr;
}

Now always use this id function, rather than just casting
the pointer to void *.

template <typename T>
void add_to_set(..., T *ptr)
{
.... insert(id(ptr));
}

I would use the type char * instead of void * because
void * has type pitfalls. If you forget to use id(ptr),
and just write ptr, it will work.

The void * type is braindamaged and should be avoided;
use char *.

// inside object_with_id
char *id() { return static_cast<char *>(id); }

Use a std::set<char *> as your registry. Now you can't just accidentally
insert any pointer into your set, because pointers don't implicitly
convert into char * (unless they are already char *).

Now if you write insert(ptr) instead of insert(id(ptr)), you get an
error when ptr is not of type char *; you are forced to use the proper
id function to reduce objects to their id of type char *.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top