S
SG
Hi!
I was toying around with some "copy-on-write" wrapper class design
(cow<>) and felt the need to use some ugly pointer casts. I was
wondering whether it is even 100% portable and if not what other
alternatives there are that don't affect the public interface of the
cow<> class template.
I basically use Boost.Function-like "type erasure" with reference
counting. I keep two pointers in the class cow<T> as data members.
One pointer to an abstract wrapper class (for cloning and ref-
counting) and another pointer T* that points directly to the wrapped
object that lives inside the wrapper. To support conversions from
cow<T> to cow<U> in case T* is convertible to U* I made the abstract
wrapper class to be a non-template. The problem arises when I need to
create a copy and adjust the 2nd pointer accordingly.
Here're some code fragments (for brevity) so you know what I'm talking
about:
--------8<----------------8<----------------8<--------
class abstract_wrapper
{
public:
abstract_wrapper() : ref_counter(1) {}
virtual ~abstract_wrapper() {}
virtual abstract_wrapper* clone() const = 0;
void refct_inc() {++ref_counter;}
bool refct_dec() {return (--ref_counter)==0;}
bool unique() const {return ref_counter<=1;}
private:
long ref_counter;
};
// .....
template<typename T>
class cow
{
private:
template<typename> friend class cow;
abstract_wrapper* paw;
T* ptr; // points to a member of *paw
void make_copy();
public:
// .....
T const& operator*() const {return *ptr;}
T const* operator->() const {return ptr;}
T& wref()
{
if (!paw->unique()) make_copy();
return *ptr;
}
// .....
};
template<typename T>
void cow<T>::make_copy()
{
assert( !paw->unique() );
typedef ..... char_t;
typedef ..... void_t;
// is T const? | char_t | void_t
// ------------+------------+-----------
// yes | const char | const void
// no | char | void
abstract_wrapper* paw2 = paw->clone();
char_t* bas1 = static_cast<char_t*>(static_cast<void_t*>(paw));
char_t* bas2 = static_cast<char_t*>(static_cast<void_t*>(paw2));
char_t* sub1 = static_cast<char_t*>(static_cast<void_t*>(ptr));
char_t* sub2 = bas2 + (sub1-bas1);
ptr = static_cast<T*>(static_cast<void_t*>(sub2));
paw->refct_dec();
paw = paw2;
}
--------8<----------------8<----------------8<--------
Obviously the private function "make_copy" looks a bit ugly with all
the casts. But as far as I can tell this should be portable. The
dynamic types of *paw and *paw2 are the same. The assumption is that
the object layout is consistent over all possible objects of that
dynamic type and that I can safely compute the address of the new
member object the way I did.
Am I correct?
Cheers!
SG
I was toying around with some "copy-on-write" wrapper class design
(cow<>) and felt the need to use some ugly pointer casts. I was
wondering whether it is even 100% portable and if not what other
alternatives there are that don't affect the public interface of the
cow<> class template.
I basically use Boost.Function-like "type erasure" with reference
counting. I keep two pointers in the class cow<T> as data members.
One pointer to an abstract wrapper class (for cloning and ref-
counting) and another pointer T* that points directly to the wrapped
object that lives inside the wrapper. To support conversions from
cow<T> to cow<U> in case T* is convertible to U* I made the abstract
wrapper class to be a non-template. The problem arises when I need to
create a copy and adjust the 2nd pointer accordingly.
Here're some code fragments (for brevity) so you know what I'm talking
about:
--------8<----------------8<----------------8<--------
class abstract_wrapper
{
public:
abstract_wrapper() : ref_counter(1) {}
virtual ~abstract_wrapper() {}
virtual abstract_wrapper* clone() const = 0;
void refct_inc() {++ref_counter;}
bool refct_dec() {return (--ref_counter)==0;}
bool unique() const {return ref_counter<=1;}
private:
long ref_counter;
};
// .....
template<typename T>
class cow
{
private:
template<typename> friend class cow;
abstract_wrapper* paw;
T* ptr; // points to a member of *paw
void make_copy();
public:
// .....
T const& operator*() const {return *ptr;}
T const* operator->() const {return ptr;}
T& wref()
{
if (!paw->unique()) make_copy();
return *ptr;
}
// .....
};
template<typename T>
void cow<T>::make_copy()
{
assert( !paw->unique() );
typedef ..... char_t;
typedef ..... void_t;
// is T const? | char_t | void_t
// ------------+------------+-----------
// yes | const char | const void
// no | char | void
abstract_wrapper* paw2 = paw->clone();
char_t* bas1 = static_cast<char_t*>(static_cast<void_t*>(paw));
char_t* bas2 = static_cast<char_t*>(static_cast<void_t*>(paw2));
char_t* sub1 = static_cast<char_t*>(static_cast<void_t*>(ptr));
char_t* sub2 = bas2 + (sub1-bas1);
ptr = static_cast<T*>(static_cast<void_t*>(sub2));
paw->refct_dec();
paw = paw2;
}
--------8<----------------8<----------------8<--------
Obviously the private function "make_copy" looks a bit ugly with all
the casts. But as far as I can tell this should be portable. The
dynamic types of *paw and *paw2 are the same. The assumption is that
the object layout is consistent over all possible objects of that
dynamic type and that I can safely compute the address of the new
member object the way I did.
Am I correct?
Cheers!
SG