> apart from critical memory intensive applications wherein the memory is really scarse,
> does it make sense to use unions anywhere??
I idea of a union exists primarily because the language is statically typed, are there are situations where we need to deal with a truly heterogeneous set of types. It is rarely used for scrounging memory.
> especially in normally application
> programming, arent there high chances of memory corruption if used wrongly??
In well-written C++ code, we almost always find that the unions used are type-safe discriminated unions - what the IS refers to as a 'union like class'.
This is the general idea:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
#include <iostream>
#include <string>
#include <new>
#include <memory>
struct int_or_str
{
bool is_int() const { return is_int_ ; }
bool is_str() const { return !is_int() ; }
~int_or_str() { if( is_str() ) str.std::string::~string() ; }
int_or_str( int v = 0 ) : i(v) {}
int_or_str( const std::string& s ) : is_int_(false) { ::new ( std::addressof(str) ) std::string(s) ; }
int_or_str( std::string&& s ) : is_int_(false) { ::new ( std::addressof(str) ) std::string( std::move(s) ) ; }
int_or_str( const char* cstr ) : is_int_(false) { ::new ( std::addressof(str) ) std::string(cstr) ; }
int_or_str( const int_or_str& that ) : is_int_( that.is_int_)
{
if( that.is_int() ) i = that.i ;
else ::new ( std::addressof(str) ) std::string( that.str ) ;
}
int_or_str( int_or_str&& that ) : is_int_( that.is_int_)
{
if( that.is_int() ) i = that.i ;
else ::new ( std::addressof(str) ) std::string( std::move(that.str) ) ;
}
int_or_str& operator= ( const int_or_str& that )
{
if( this != std::addressof(that) )
{
if( is_str() && that.is_str() ) str = that.str ;
else
{
if( is_str() ) str.std::string::~string() ;
is_int_ = that.is_int() ;
if( is_int() ) i = that.i ;
else ::new ( std::addressof(str) ) std::string( that.str ) ;
}
}
return *this ;
}
int_or_str& operator= ( int_or_str&& that )
{
if( this != std::addressof(that) )
{
if( is_str() && that.is_str() ) str = std::move(that.str) ;
else
{
if( is_str() ) str.std::string::~string() ;
is_int_ = that.is_int() ;
if( is_int() ) i = that.i ;
else ::new ( std::addressof(str) ) std::string( std::move(that.str) ) ;
}
}
return *this ;
}
/*explicit*/ operator int() const { if( !is_int() ) throw "whatever" ; return i ; }
/*explicit*/ operator int&() { if( !is_int() ) throw "whatever" ; return i ; }
/*explicit*/ operator const std::string&() const { if( is_int() ) throw "whatever" ; return str ; }
/*explicit*/ operator std::string&() { if( is_int() ) throw "whatever" ; return str ; }
private:
bool is_int_ = true ; // discriminant
union // anonymous union
{
std::string str ;
int i ;
};
};
std::ostream& operator<< ( std::ostream& stm, const int_or_str& v )
{
if( v.is_int() ) return stm << int(v) ;
else return stm << std::string(v) ;
}
|
We can use the union like class it in a completely type safe manner. In practice, we would just use a library; for instance
boost::any
or
boost::variant
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
int main()
{
{
int_or_str v = "hello" ;
std::cout << v << ' ' ;
std::string str = v ;
v = 123 ;
std::cout << v << ' ' ;
v = str ;
std::cout << v << '\n' ;
}
{
// in practice, we use a library
// #include <boost/variant.hpp>
boost::variant< std::string, int > v = "hello" ;
std::cout << v << ' ' ;
std::string str = boost::get<std::string>(v) ;
v = 123 ;
std::cout << v << ' ' ;
v = str ;
std::cout << v << '\n' ;
}
}
|