edit: forget my previous text - it was probably just confusing
here is the new version:
my question is just: is this solution "safe" (so would it work with objects of arbitrary type)? or is there something i missed out.
especially these static template functions look a bit strange in my eyes.
(they either call the destructor or copy constructor)
since the type gets "lost" after assignment, template functions which handle destruction and copying of the object are instanciated, and function pointers are kept to them. i wonder if that is safe... (see source code below)
so here is the story:
i ran into the problem that i have a container (a stack to be explicit) which needs to hold elements of multiple types. so usually i would use a union for that:
Code:
union MyUnion {
char *p;
double d;
};
std::stack< MyUnion > my_stack;
the problem: unions cant hold elements with a constructor - so placing e.g.
iterators into a union is not really possible.
of course it would be possible to put a pointer to that iterator into the union.
but since iterators are lightweight objects (only a few bytes in size) it would be pretty inefficient creating a copy of it via new and storing the pointer in the union.
so i want to store the e.g. iterator itself as a stack element.
my solution was creating the following class
which allowed the following:
Code:
--- file: main.cpp
#include <iostream>
#include <stack>
#include "union.h"
int main()
{
typedef Union<128> MyUnion;
typedef std::stack<MyUnion> MyStack;
MyStack ms;
ms.push(MyUnion(double(3.14)));
ms.push(MyUnion(int(2)));
ms.push(MyUnion(std::string("hello world\n")));
std::cout << ((std::string&)ms.top()) << "\n";
ms.pop();
std::cout << ((int&)ms.top()) << "\n";
ms.pop();
std::cout << ((double&)ms.top()) << "\n";
ms.pop();
return 0;
}
output
Code:
[Union] assigning an object of type d
[Union] using reference cast
[Union] creating a copy of type d
[Union] deleting an object of type d
[Union] assigning an object of type i
[Union] using reference cast
[Union] creating a copy of type i
[Union] deleting an object of type i
[Union] assigning an object of type Ss
[Union] using reference cast
[Union] creating a copy of type Ss
[Union] creating a copy of type Ss
[Union] deleting an object of type Ss
[Union] deleting an object of type Ss
[Union] using reference cast
hello world
[Union] deleting an object of type Ss
[Union] using reference cast
2
[Union] deleting an object of type i
[Union] using reference cast
3.14
[Union] deleting an object of type d
[Union] assigning an object of type i
[Union] deleting an object of type i
note that the attributes of MyUnion are just an array where the object is kept in (in this case: int, double, std::string) and 2 function pointers.
accessing the object works via conversions (instead of union.member of ordinary c-unions)
(so instead of my_union.as_string its (std::string)my_union
but this is not much of a problem - either you must remember which is the right member - or what is the right conversion.
here is the source code of the "union"
Code:
--- file: union.h
#ifndef __UNION_H
#define __UNION_H
// Tue, Dec 6, 03:17 - alpha done!
/**
i guess this is a pretty unsafe class - but yet useful in some situations
where the ordinary c-union fails.
some rules:
.) Union u; u = OBJECT THAT IS NOT A UNION;
this causes u to hold a COPY of that OBJECT
.) Union u; u = ANOTHER UNION
this causes u to hold a COPY of the OBJECT containd in the OTHER UNION -
NOT THE OTHER UNION ITSELF!!!
.) before ANY access-conversions are valid, an object needs to be assigned
to the union.
so there needs to be a Union u = some object; statement before
e.g. (int&)u is valid. doing so without the assignment raises
an exception
well... exceptions have been removed here...
**/
#include <typeinfo>
template <unsigned SIZE> class Union {
protected:
typedef char Data[SIZE];
Data data;
void (*pfn_destroy )(Union &r_union);
void (*pfn_copy )(Union &r_dest, const Union &r_src);
public:
Union() :
pfn_destroy(NULL),
pfn_copy (NULL)
{
}
Union(const Union &r_other) :
pfn_destroy(NULL),
pfn_copy (NULL)
{
r_other.pfn_copy(*this, r_other);
}
template <typename T> Union (const T &r) :
pfn_destroy(NULL),
pfn_copy (NULL)
{
this->operator = (r);
}
template <typename T> static void destroy(Union &r_union)
{
std::cout << "[Union] deleting an object of type "
<< typeid(T).name() << "\n";
reinterpret_cast<T*>(&r_union.data[0])->~T();
r_union.pfn_destroy = NULL;
r_union.pfn_copy = NULL;
}
template <typename T> static void copy(Union &r_dest,
const Union &r_src)
{
std::cout << "[Union] creating a copy of type "
<< typeid(T).name() << "\n";
T* p_dest_data = reinterpret_cast<T*> (&r_dest.data[0]);
const T* p_src_data = reinterpret_cast<const T*>(&r_src.data [0]);
new(p_dest_data) T(*p_src_data);
r_dest.pfn_destroy = r_src.pfn_destroy;
r_dest.pfn_copy = r_src.pfn_copy;
}
Union& operator = (const Union &r_rhs)
{
// destroy old content
if(NULL != pfn_destroy)
pfn_destroy(*this);
if(NULL != r_rhs.pfn_copy)
r_rhs.pfn_copy(*this, r_rhs);
}
template <typename T> T& operator = (const T& r_rhs)
{
// destroy old content of data
if(NULL != pfn_destroy)
pfn_destroy(*this);
// resize data
if(sizeof(T) > sizeof(data)) {
std::cout << "[Union] assignment failed: object too big\n";
// throw some exception
}
std::cout << "[Union] assigning an object of type "
<< typeid(T).name() << "\n";
// use placement new in order to create a new object
// <todo> note that alignment is not considered here... </todo>
new(&data[0]) T(r_rhs);
// set up the delete function
pfn_destroy = &destroy<T>;
// set up the copy function
pfn_copy = ©<T>;
return *(reinterpret_cast<T*>(&data[0]));
}
template <typename T> operator T* ()
{
std::cout << "[Union] using pointer cast\n";
return reinterpret_cast<T*>(&data[0]);
}
template <typename T> operator T& ()
{
std::cout << "[Union] using reference cast\n";
return reinterpret_cast<T&>(data[0]);
}
~Union()
{
// destroy content of data
if(NULL != pfn_destroy)
pfn_destroy(*this);
}
};
#endif // __UNION_H