Re: Is this portable? [static pointer casts, char* arithmetic]
On 14 Apr., 18:03, "Alf P. Steinbach" <al...@start.no> wrote:
Think about separating concerns.
Why should the same class have responsibility for cloning and reference c=
ounting?
If I separated this it would not solve the problem, only replace one
to-be-managed object with two to-be-managed objects. In order to
support runtime polymorphism as well -- yes, it's probably overkill, i
know -- I need a wrapper with a virtual clone() anyways. So, why NOT
combining clonable wrapper with ref-counter? :-p
Also, when you do reference counting you should really not allow the refe=
rence
count to drop to zero, as implied by your 'unique' implementation. When t=
he
reference count drops to zero, self-destroy. That is, after all, the poin=
t.
True. I just avoided writing "delete this;" for style reasons. Since
there's only one place where I need to check whether I need to delete
it (the destructor) it doesn't bother me.
T* ptr; // points to a member of *paw
Why a member of?
Goals:
- copy-on-write wrapper "cow<>" for value types
- supports conversions from cow<U> to cow<T> in case
Convertible<U*,T*>
- manage the life-time of only one heap allocated object
I think the 2nd requirement implies decltype(paw) to be independent
from U/T. Otherwise I wouldn't need these address calculations and
could just ask the wrapper for the pointer to its member.
[...]
OK so far, assuming you have just left out declarations of copy assignmen=
t and
copy construction.
Of course.
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*>(p=
aw));
char_t* bas2 = static_cast<char_t*>(static_cast<void_t*>(p=
aw2));
char_t* sub1 = static_cast<char_t*>(static_cast<void_t*>(p=
tr));
char_t* sub2 = bas2 + (sub1-bas1);
Offset calculations are only formally well-defined for PODs, which this i=
sn't.
Hmmm... I should have expected that.
The clone function either returns a pointer that can be downcasted to T*,=
or it
doesn't, in which case it returns too little information.
The 'clone' function returns a pointer to an abstract_wrapper that
contains a T object (or some U object where Convertible<U*,T*>).
ptr = static_cast<T*>(static_cast<void_t*>(sub2));
paw->refct_dec();
Client code has no business messing with internal reference counts.
No, of course not. But this wasn't "client code". It was a private
member function of cow<T> with the sole purpose of creating a copy of
the pointee (copy on write).
Instead use a boost::intrusive_ptr for 'paw'.
Of course I could make abstract_wrapper compatible with
intrusive_ptr. I don't see the advantage, though.
[...]
Formally it's debatable, that is, whether the compiler is allowed to plac=
e some
part of an object some unrelated place in memory and just include an offs=
et or
pointer or something. It can do that for virtual multiple inheritance. Th=
e
I was under the impression that the compiler is required to use a
consequtive sequence of sizeof(T) bytes to represent the (whole)
object.
[...]
In practice it's well-defined, as long as you're dealing with complete ob=
jects.
What do you mean? The dynamic type of *paw is never mentioned
anywhere. Are you saying that I used a construct in "make_copy" that
would require T to be a complete type? The dynamic type of *paw is
something along the lines of concrete_wrapper<U> which publicly
inherits from abstract_wrapper and U might not be the same as T.
If OTOH you're cloning an object that's a base class sub-object of anothe=
r
object and that inherits virtually from some base class, and that virtual=
base
class sub-object is your T*, then all bets are off. But presumably that's=
not
I honestly don't know what you're talking about. There's no virtual
inheritence involved (excluding the set of possible T's). The object
that is cloned is a "concrete_wrapper<U>" which has a member of type
U.
what you're doing. However, the fact that you're dealing separately with =
the paw
and the ptr, not simply having the same pointer of 2 different types, see=
ms to
indicate that your design doesn't properly enforce identity of these poin=
ters.
They are not identical. It's just that *ptr is a data member from the
dynamic object *paw whose type has been erased to support
conversions. I believe the standard shared_ptr implementation also
stores two pointers. One pointer to the object that contains the ref-
counter and deleter and one pointer to the object being managed. The
difference here is that I merged them for reasons earlier mentioned.
But as I hope the comments above make clear, a better solution is to re-d=
esign
so that you have available the required information.
The missing information, the presence of the casts, indicates some design=
flaw.
I wasn't satisfied with the design, either. The casting part bugged
me. I wouldn't go as far and say the presence of casts implies a bad
design. They can be useful, too. I think I just tried to hard.
Maybe there is no solution that meets all the goals I mentioned above
that avoids this pointer arithmetic. At least I don't see any.
Cheers!
SG