Object layout guarantees and manual pointer adjustments

  • Follow


Hello!

This is a question on object layout guarantees and whether manual
pointer adjustments can be done safely.  What does the C++ standard
say about this:

    ptrdiff_t delta(void* from, void* to)
    {
       return static_cast<char*>(to)
             -static_cast<char*>(from);
    }

    void* adjust(void* in, ptrdiff_t diff)
    {
       return static_cast<char*>(in) + diff;
    }

    class A { int a; /* assume non-POD */ };
    class B { int b; /* assume non-POD */ };
    class C : public A, public B {};

    void foo() {
       C c;
       B* pb = &c;
       ptrdiff_t diff_c2b = delta(&c,pb);
       C c2;
       B* x1 = &c2;
       B* x2 = static_cast<B*>(adjust(&c2,diff_c2b));
       assert(x1==x2);
    }

Is the assertion guaranteed to hold?  I believe that it does hold for
all existing implementations but I would like to know whether this is
really guaranteed by the C++ standard.

The reason why I'm interested in this is because this would enable me
to convert pointers in case the information about the source pointer
type is only available at runtime as reference to a typeinfo object
and/or as a ptrdiff_t like in the above example.

Cheers!
SG

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/16/2009 8:07:07 AM

On Apr 16, 7:07 am, SG <s.gesem...@gmail.com> wrote:
> This is a question on object layout guarantees and whether manual
> pointer adjustments can be done safely.  What does the C++ standard
> say about this:
>
>     ptrdiff_t delta(void* from, void* to)
>     {
>        return static_cast<char*>(to)
>              -static_cast<char*>(from);
>     }
>
>     void* adjust(void* in, ptrdiff_t diff)
>     {
>        return static_cast<char*>(in) + diff;
>     }
>
>     class A { int a; /* assume non-POD */ };
>     class B { int b; /* assume non-POD */ };
>     class C : public A, public B {};
>
>     void foo() {
>        C c;
>        B* pb = &c;
>        ptrdiff_t diff_c2b = delta(&c,pb);
>        C c2;
>        B* x1 = &c2;
>        B* x2 = static_cast<B*>(adjust(&c2,diff_c2b));
>        assert(x1==x2);
>     }
>
> Is the assertion guaranteed to hold?  I believe that it does hold for
> all existing implementations but I would like to know whether this is
> really guaranteed by the C++ standard.

I don't think so. Among other things, C++ standard does not even
guarantee that a non-POD object is allocated as a contiguous block of
memory - so it would seem to be possible for some implementation to
allocate base class subobjects separately (and then the pointer
difference won't hold any meaning, anyway).


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Pavel 4/16/2009 4:50:49 PM


On 16 Apr., 16:07, SG <s.gesem...@gmail.com> wrote:
> This is a question on object layout guarantees and whether manual
> pointer adjustments can be done safely.
> [...]
> The reason why I'm interested in this is because this would enable me
> to convert pointers in case the information about the source pointer
> type is only available at runtime [...]

Assuming B and C are non-const types/classes/structs, C* is implicitly
convertible to B*, 'pc' is a
valid pointer to C, and 'pb' is a pointer to B converted from 'pc'.

I guess I'm basically asserting that

  1. "referable via raw pointers"

     pb and pc are both losslessly convertible to a char*
     and back again by means of static_casts or reinterpret_casts.
     pb -> rb -> pb,
     pc -> rc -> pc  with rb and rc being pointers to char.


  2. "consistency of layout"

     The pointer difference rc-rb is implementation-defined but valid
     and constant over all possible objects of type C the pointer pc
     may point to (assuming pb is always converted from pc).


I think the first one is guaranteed by the C++ standard.  I'm not sure
about the second assertion but it seems like a reasonable assumption
worth guaranteeing.


Cheers!
SG

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/16/2009 11:02:39 PM

On Apr 16, 7:07 am, SG <s.gesem...@gmail.com> wrote:
> Hello!
>
> This is a question on object layout guarantees and whether manual
> pointer adjustments can be done safely.  What does the C++ standard
> say about this:
>
>     ptrdiff_t delta(void* from, void* to)
>     {
>        return static_cast<char*>(to)
>              -static_cast<char*>(from);
>     }
>
>     void* adjust(void* in, ptrdiff_t diff)
>     {
>        return static_cast<char*>(in) + diff;
>     }
>
>     class A { int a; /* assume non-POD */ };
>     class B { int b; /* assume non-POD */ };
>     class C : public A, public B {};
>
>     void foo() {
>        C c;
>        B* pb = &c;
>        ptrdiff_t diff_c2b = delta(&c,pb);
>        C c2;
>        B* x1 = &c2;
>        B* x2 = static_cast<B*>(adjust(&c2,diff_c2b));
>        assert(x1==x2);
>     }
>
> Is the assertion guaranteed to hold?  I believe that it does hold for
> all existing implementations but I would like to know whether this is
> really guaranteed by the C++ standard.

That particular program may be guaranteed not to trigger that assert,
but there is a gotcha you should know: virtual multiple inheritance.
Depending on the type of the complete object, various base class sub
objects will be positioned differently relative to each other in the
complete object. So for objects of the same complete type, maybe it
will work, but probably not by standard. However, for arbitrary
pointers to objects, thus including pointers to base class sub
objects, no, an assert like the above will not always work on any
implementation for complete objects of different types.


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply joshuamaurice 4/17/2009 8:13:16 AM

On 17 Apr., 00:50, Pavel Minaev <int...@gmail.com> wrote:
> On Apr 16, 7:07 am, SG <s.gesem...@gmail.com> wrote:
> > Is the assertion guaranteed to hold?  [...]
> I don't think so. Among other things, C++ standard does not even
> guarantee that a non-POD object is allocated as a contiguous block of
> memory

Really?

Well, there's "�1.8 object model" (N2857.pdf) which says

  "[...] an object of of trivially copyable or standard-layout
   type (3.9) shall occupy contiguous bytes of storage [...]"

which seems to suggest that for non-PODs this might not be the case.
But there's also

  "[...] an object is a region of storage [...]"
  "[...] object is created by [...] or by a new expression [...]"

The term "region" is a bit fuzzy for my taste that can be understood
as contiguous bytes of storage as well.  If the standard allowed a
"region" to be a union of disconnected blocks of bytes this would
imply that either one of the following statements must be true

 1. There may be multiple calls to allocation functions for each
    new-expression even though they may not call each other (one
    call for each contiguous block of bytes).
 2. the user can only control allocation for a fraction of the
    object's storage region.  The remaining bits are handled by
    some other intrinsic allocation mechanism (compiler magic)

2 seems not likely because it would undermine the purpose of
overridable new/delete operators.  I'm not sure what the standard says
about 1.  Until now, I thought that only one allocation function call
is generated by the compiler directly.

Does anybody know an implementation that splits an object's storage
region across multiple disconnected blocks of bytes?  What motivation
can there be for implementing it that way?

In case of a continguous block of bytes the raw poitner difference
might still not be constant due to some indirections.  For example, a
compiler might choose to use different layouts for different instances
of the same type -- i.e. placing multiple virtual base class subobject
in any order as long as alignment constraints are met:

   class A { int a; };
   class B { int b; };
   class C : virtual A, virtual B { int foo; };

where some objects of type C may store base class ubobject A before B
and for some objects of type C it's the other way around.  But it
would require including a "virtual table" directly in the object of
type C instead of a pointer to this "vtable" that contains a struct
shared among all instances that specifies the layout.  I don't see
compelling reasons for allowing non-consistent layout.  If you know
any, please enlighten me.


Cheers!
SG


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/17/2009 8:14:49 AM

SG wrote:
> where some objects of type C may store base class subobject A before B
> and for some objects of type C it's the other way around.  But it
> would require including a "virtual table" directly in the object of
> type C instead of a pointer to this "vtable" that contains a struct
> shared among all instances that specifies the layout.  I don't see
> compelling reasons for allowing non-consistent layout.  If you know
> any, please enlighten me.

The following code triggers an assertion failure, both after compiling
with GCC or with MSVC:
___________________________________________________
class A { int a; };
class B { int b; };
class C : public A, public virtual B {};

void foo(C& c1, C& c2)
{
   B* pb = &c1;
   std::ptrdiff_t diff_c2b = delta(&c1, pb);
   B* x1 = &c2;
   B* x2 = static_cast<B*>(adjust(&c2, diff_c2b));
   assert(x1 == x2);
}

class D : public C { int d; };

int main()
{
   C c;
   D d;
   foo(c, d);
   return 0;
}
___________________________________________________

The offset of the B sub-object within a C object varies in function of
the dynamic type of the latter.

Falk

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply ISO 4/17/2009 10:36:20 AM

On 17 Apr., 16:13, joshuamaur...@gmail.com wrote:
>
> That particular program may be guaranteed not to trigger that assert,
> but there is a gotcha you should know: virtual multiple inheritance.
> Depending on the type of the complete object, various base class sub
> objects will be positioned differently relative to each other in the
> complete object. So for objects of the same complete type, maybe it
> will work, but probably not by standard. However, for arbitrary
> pointers to objects, thus including pointers to base class sub
> objects, no, an assert like the above will not always work on any
> implementation for complete objects of different types.

Thank you for your response.  I havn't thought of this case.

But these kinds of pointers are not implicitly convertible.  So, this
case doesn't apply here.


Cheers!
SG

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/17/2009 10:38:59 AM

On 17 Apr., 18:36, Falk Tannh�user <falk.tannhau...@crf.canon.fr>
wrote:
>
> The following code triggers an assertion failure, both after compiling
> with GCC or with MSVC:

[multiple + virtual inheritence example]

> The offset of the B sub-object within a C object varies in function of
> the dynamic type of the latter.

Thank you for pointing out this interesting example.  Even though I
considered only identical dynamic types (see my example, I guess I
didn't explicitly disallow other cases) it's a good argument for not
messing around with pointers in that way.

The actual layout probably looks something like this for GCC and MSVC:

  C:  vptr A B
  D:  vptr A D B

where the information about the location of B relative to the the non-
virtual base class subobject A is reachable via 'vptr'.

But I think it's safe to assume -- even though it might not be
formally guaranteed -- that an object 'o' of type 'T' with typeid(o)
==typeid(T) (i.e. "complete", T == most-derived) occupies a
consecutive sequence of bytes, and that different instances of the
exact same dynamic type share the exact same layout.


Cheers!
SG


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/17/2009 2:09:45 PM

Pavel Minaev wrote:

>> Is the assertion guaranteed to hold?  I believe that it does hold for
>> all existing implementations but I would like to know whether this is
>> really guaranteed by the C++ standard.
>
> I don't think so. Among other things, C++ standard does not even
> guarantee that a non-POD object is allocated as a contiguous block of
> memory - so it would seem to be possible for some implementation to
> allocate base class subobjects separately (and then the pointer
> difference won't hold any meaning, anyway).


The base memory for a complete object must be contiguous. If this was
not the case then it would be impossible to create objects dynamically
with placement new (the version where the programmer provides the memory)

Subobjects of a base sub-object might not be contiguous, indeed with
virtual bases it is impossible to make this a requirement. This is one
of the places hwere we must distinguish between complete objects and
sub-objects.

--
Note that robinton.demon.co.uk addresses are no longer valid.

      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Francis 4/17/2009 2:12:14 PM

On Apr 17, 7:14 am, SG <s.gesem...@gmail.com> wrote:
> On 17 Apr., 00:50, Pavel Minaev <int...@gmail.com> wrote:
> > I don't think so. Among other things, C++ standard does not even
> > guarantee that a non-POD object is allocated as a contiguous block of
> > memory
>
> But there's also
>
>   "[...] an object is a region of storage [...]"
>   "[...] object is created by [...] or by a new expression [...]"
>
> The term "region" is a bit fuzzy for my taste that can be understood
> as contiguous bytes of storage as well.  If the standard allowed a
> "region" to be a union of disconnected blocks of bytes this would
> imply that either one of the following statements must be true
>
>  1. There may be multiple calls to allocation functions for each
>     new-expression even though they may not call each other (one
>     call for each contiguous block of bytes).
>  2. the user can only control allocation for a fraction of the
>     object's storage region.  The remaining bits are handled by
>     some other intrinsic allocation mechanism (compiler magic)
>
> 2 seems not likely because it would undermine the purpose of
> overridable new/delete operators.  I'm not sure what the standard says
> about 1.  Until now, I thought that only one allocation function call
> is generated by the compiler directly.
>
> Does anybody know an implementation that splits an object's storage
> region across multiple disconnected blocks of bytes?  What motivation
> can there be for implementing it that way?

Have a look at "A Design Rationale for C++/CLI" by Herb Sutter
(available here: http://www.gotw.ca/publications/C++CLIRationale.pdf).
It has this interesting tidbit in [3.1.3.3]:

"I remember the day I showed the idea for this potential unification
on Bjarne Stroustrup�s blackboard. I started to draw a picture like
[N1557] slide 52, and without waiting for me to finish Bjarne went to
his bookshelf and opened a book to show where, despite criticism, he
had always insisted that C++ must not require objects to be laid out
contiguously in a single memory block. Others had often questioned the
usefulness of C++�s leaving that implementation door open for the
future, but the mixed type design � where parts of the same logical
object must be on different physical heaps � vindicates Bjarne�s
design judgment"

I'm not aware of anyone actually implementing this, though.


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Pavel 4/17/2009 7:06:53 PM

On Apr 17, 1:12 pm, Francis Glassborow
<francis.glassbo...@btinternet.com> wrote:
> > I don't think so. Among other things, C++ standard does not even
> > guarantee that a non-POD object is allocated as a contiguous block of
> > memory - so it would seem to be possible for some implementation to
> > allocate base class subobjects separately (and then the pointer
> > difference won't hold any meaning, anyway).
>
> The base memory for a complete object must be contiguous. If this was
> not the case then it would be impossible to create objects dynamically
> with placement new (the version where the programmer provides the memory)

Does placement new actually guarantees that object will not use any
memory outside of that provided to placement new? The only guarantee I
can find in the Standard text about placement operator new is that it
will simply return the pointer passed to it as an argument. At the
same time, I don't see any requirement that all memory allocated by
implementation for its purposes should be allocated using operator new
(it definitely has to allocate memory for the complete object using
operator new, though). Neither do I see a requirement that the storage
region for the complete object must physically include storage regions
for subobjects.


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Pavel 4/17/2009 7:07:11 PM

Francis Glassborow <francis.glassborow@btinternet.com> wrote:
> Pavel Minaev wrote:
>
>>> Is the assertion guaranteed to hold?  I believe that it does hold for
>>> all existing implementations but I would like to know whether this is
>>> really guaranteed by the C++ standard.
>>
>> I don't think so. Among other things, C++ standard does not even
>> guarantee that a non-POD object is allocated as a contiguous block of
>> memory - so it would seem to be possible for some implementation to
>> allocate base class subobjects separately (and then the pointer
>> difference won't hold any meaning, anyway).
>
> The base memory for a complete object must be contiguous. If this was
> not the case then it would be impossible to create objects dynamically
> with placement new (the version where the programmer provides the memory)

I do not think this is true, but see my comment below; it depends on
what we mean by `contiguous'.

Ref. c++98, 9.2, point 12:

     Nonstatic data members of a (non-union) class declared without
     an intervening access-specifier are allocated so that later
     members have higher addresses within a class object. The
     order of allocation of nonstatic data members separated by an
     access-specifier is unspecified (11.1).  Implementation alignment
     requirements might cause two adjacent members not to be allocated
     immediately after each other; so might requirements for space for
     managing virtual functions (10.3) and virtual base classes (10.1).

You might have meant that the whole object is contiguous, if one
includes the additional spacing which a compiler is allowed to insert
(that is, it depends what we mean by `contiguous').  I think but have
not verified that this is true.  Given the OPs question though, I'm not
sure that such a guarantee, true or not, would be useful.

Cheers,

-tom

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply tf 4/17/2009 7:07:42 PM

tf@42.sci.utah.edu wrote:
> Francis Glassborow <francis.glassborow@btinternet.com> wrote:
>> Pavel Minaev wrote:
>>
>>>> Is the assertion guaranteed to hold?  I believe that it does hold for
>>>> all existing implementations but I would like to know whether this is
>>>> really guaranteed by the C++ standard.
>>> I don't think so. Among other things, C++ standard does not even
>>> guarantee that a non-POD object is allocated as a contiguous block of
>>> memory - so it would seem to be possible for some implementation to
>>> allocate base class subobjects separately (and then the pointer
>>> difference won't hold any meaning, anyway).
>> The base memory for a complete object must be contiguous. If this was
>> not the case then it would be impossible to create objects dynamically
>> with placement new (the version where the programmer provides the memory)
>
> I do not think this is true, but see my comment below; it depends on
> what we mean by `contiguous'.
>
> Ref. c++98, 9.2, point 12:
>
>      Nonstatic data members of a (non-union) class declared without
>      an intervening access-specifier are allocated so that later
>      members have higher addresses within a class object. The
>      order of allocation of nonstatic data members separated by an
>      access-specifier is unspecified (11.1).  Implementation alignment
>      requirements might cause two adjacent members not to be allocated
>      immediately after each other; so might requirements for space for
>      managing virtual functions (10.3) and virtual base classes (10.1).
>
> You might have meant that the whole object is contiguous, if one
> includes the additional spacing which a compiler is allowed to insert
> (that is, it depends what we mean by `contiguous').  I think but have
> not verified that this is true.  Given the OPs question though, I'm not
> sure that such a guarantee, true or not, would be useful.
>
> Cheers,
>

When saying that a complete object must occupy contiguous bytes
(remember I am referring to base memory not to memory held through
pointers) I have not said, nor would I say that there can be no padding.
However padding is part of the object. It is even possible for padding
in a base sub-object can be recycled for use in a derived object. E.g.
(AFAIK):

struct base {
    char c;
    int i;
};

struct derived: public base {
    char c1;
};

can have the sizeof both base and derived the same because on a system
where alignment considerations result in padding between c and i can use
some of that padding for c1.


I cannot imagine how a system might work where the value of sizeof(T)
was the result of summing two or more non-contiguous chunks of storage.

I think the following code must always work for all types T (given
enough memory is available)

int main(){
    void* mem_ptr = operator new( sizeof(T));
    T* T_obj = new (mem_ptr) T;
}

Now explain, if you can, how that could work if the base memory for T
could be non-contiguous.

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Francis 4/18/2009 4:42:47 PM

On 18 Apr., 03:07, t...@42.sci.utah.edu wrote:
> Francis Glassborow <francis.glassbo...@btinternet.com> wrote:
>
> > The base memory for a complete object must be contiguous. If this was
> > not the case then it would be impossible to create objects dynamically
> > with placement new (the version where the programmer provides the memory)
>
> I do not think this is true, but see my comment below; it depends on
> what we mean by `contiguous'.

[...]

> You might have meant that the whole object is contiguous, if one
> includes the additional spacing which a compiler is allowed to insert
> (that is, it depends what we mean by `contiguous').  I think but have
> not verified that this is true.

Yes, that's probably what he meant.  To summarize this assumption in
my own words:

   "A complete object 'o' (complete = it's not a base class subobject
    of another object) is completely contained within a consecutive
    sequence of bytes that starts at the address static_cast<void*>(&o)
    and is of length sizeof(o)."

I don't think the C++ standard leaves room for anything else
considering custom new operators, placement new, etc.

What's left is: Is the layout guaranteed to be consistent among
different instances of the same *complete* type?

> Given the OPs question though, I'm not sure that such a guarantee,
> true or not, would be useful.

I understand the skepticism.  Usually, when someone asks questions
like these, there's a much better alternative.  I wouldn't have
thought that such guarantees can be useful until recently.

I was trying to write (explore) something like this:
http://img245.imageshack.us/img245/5633/cow.png
The polymorphic requirement forced me to use an abstract class that
doesn't mention any template type parameters in its interface.  The
problem arises when I clone the "wrapper" object pointed to via
"paw".  Consider this:

   concrete_wrapper<U>* pcw = new concrete_wrapper<U>(...);
   T* ptr1 = pcw->get(); // returns address of wrapped object that
                         // is convertible to T*. The function 'get'
                         // is not part of the abstract interface
   abstract_wrapper* paw1 = pcw;

   // we now "forget" what the type U was and only
   // have 'paw1' and 'ptr1' available at runtime.

   abstract_wrapper* paw2 = paw1->clone();
   T* ptr2 = ???

The dynamic type of the new wrapper object pointed to by "paw2" is
known to be exactly the same as the dynamic type of the object pointed
to by "paw1".  But the type information about U is gone.  Still, I'd
like to be able to initialize "ptr2" with the correct value so that it
points to the new copy of the cloned member object.  In case the
layouts are known to be equal this "manual pointer adjustment" trick
from the first post could be applied to compute the correct value for
"ptr2".

Admittedly, it's a rather ugly "solution".  But so far I wasn't able
to come up with a less ugly implementation that meets all the design
goals.  It's more of a puzzle right now.  I'm aware that it may not be
very useful in practise.


Cheers!
SG


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/18/2009 4:43:14 PM

On Apr 18, 3:42 pm, Francis Glassborow
<francis.glassbo...@btinternet.com> wrote:
> I cannot imagine how a system might work where the value of sizeof(T)
> was the result of summing two or more non-contiguous chunks of storage.
>
> I think the following code must always work for all types T (given
> enough memory is available)
>
> int main(){
>     void* mem_ptr = operator new( sizeof(T));
>     T* T_obj = new (mem_ptr) T;
>
> }
>
> Now explain, if you can, how that could work if the base memory for T
> could be non-contiguous.

By allocating any additional memory during the evaluation of the
expression "new T", and not counting that memory for the purpose of
sizeof().


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Pavel 4/19/2009 2:58:38 AM

On 19 Apr., 10:58, Pavel Minaev <int...@gmail.com> wrote:
> On Apr 18, 3:42 pm, Francis Glassborow
>
> <francis.glassbo...@btinternet.com> wrote:
> > I cannot imagine how a system might work where the value of sizeof(T)
> > was the result of summing two or more non-contiguous chunks of storage.
>
> > I think the following code must always work for all types T (given
> > enough memory is available)
>
> > int main(){
> >     void* mem_ptr = operator new( sizeof(T));
> >     T* T_obj = new (mem_ptr) T;
>
> > }
>
> > Now explain, if you can, how that could work if the base memory for T
> > could be non-contiguous.
>
> By allocating any additional memory during the evaluation of the
> expression "new T", and not counting that memory for the purpose of
> sizeof().

True.  You could place a virtual base class suboject somewhere else
for example.  But it would require a 2nd allocation that is either
done transparently behind the scenes or a via the global new operator
that might be a user-provided function.  So, you either prevent the
user from taking control over allocation (which goes against the very
idea of making new/delete overloadable) or a user-declared global new
operator is called multiple times with varying size parameters for
just one new-expression which makes this behaviour observable.

I also fail to see the merit in allowing that.  It seems it would make
an implementation only more complex without any advantages.  If that's
true, adding these guarantees explicitly won't hurt.

Cheers!
SG


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/19/2009 5:46:57 PM

Pavel Minaev wrote:
> On Apr 18, 3:42 pm, Francis Glassborow
> <francis.glassbo...@btinternet.com> wrote:
>> I cannot imagine how a system might work where the value of sizeof(T)
>> was the result of summing two or more non-contiguous chunks of storage.
>>
>> I think the following code must always work for all types T (given
>> enough memory is available)
>>
>> int main(){
>>     void* mem_ptr = operator new( sizeof(T));
>>     T* T_obj = new (mem_ptr) T;
>>
>> }
>>
>> Now explain, if you can, how that could work if the base memory for T
>> could be non-contiguous.
>
> By allocating any additional memory during the evaluation of the
> expression "new T", and not counting that memory for the purpose of
> sizeof().
>
Effectively that would be using dynamic memory for part of an object. I
doubt that such is allowed to a conforming implementation. I am also
pretty certain that no existing implementation does any such thing.


--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Francis 4/20/2009 1:49:04 PM

Hi!

SG schrieb:
> Admittedly, it's a rather ugly "solution".  But so far I wasn't able
> to come up with a less ugly implementation that meets all the design
> goals.  It's more of a puzzle right now.  I'm aware that it may not be
> very useful in practise.

Ok, I was able to clone all information and not rely on object layout. I
introduced a new class hierachy of converter objects, that implement a
function from abstract_wrapper* to T& by means of a simply linked list
of converters. Every time a cow<T> is constructed from a cow<U> a
converter from U& to T& is instantiated and added to the list. Then you
can retrieve the cloned T& from the new instance of abstract_wrapper.

The downside is the long execution path on converting cow<T>. Memory
allocation can be reduced, I think.

Code follows:

#include <iostream>
#include <ostream>
#include <string>

namespace proposed
{

	class abstract_wrapper
	{
	public:
		virtual ~abstract_wrapper() {}
		virtual abstract_wrapper* clone() =0;
	};

	template<typename U>
	class concrete_wrapper
		: public abstract_wrapper
	{
		U u;
	public:
		U& get() { return u; }
		
		concrete_wrapper<U>* clone()
			{ return new concrete_wrapper<U>(*this); }
	};

	template<typename TargetType>
	struct converter_base
	{
		converter_base() {}
		virtual ~converter_base() {}
		virtual converter_base* clone() =0;
		virtual TargetType& get(abstract_wrapper*) =0;
	protected:
		converter_base(converter_base const&) {}
	};

	template<typename SourceType>
	struct root_converter : converter_base<SourceType>
	{
		root_converter<SourceType>* clone()
		{
			return new root_converter<SourceType>();
		}
		
		SourceType& get(abstract_wrapper* const aw)
		{
			return static_cast<concrete_wrapper<SourceType>*>(aw)->get();
		}
	};

	template<typename TargetType, typename SourceType>
	struct converter : converter_base<TargetType>
	{
		typedef converter_base<SourceType> referred_type;
		
		converter(referred_type* next)
			: next(next)
		{}
		~converter() { delete next; }

		converter<TargetType, SourceType>* clone()
		{
			return new converter<TargetType, SourceType>(next->clone());
		}
		TargetType& get(abstract_wrapper* const aw)
		{
			return next->get(aw);
		}
	private:
		referred_type* next;
		converter(converter const&) /*{}*/ ;
	};
	
	template<typename T>
	class cow
	{
		abstract_wrapper* wrapper;
		converter_base<T>* converter;

	public:
		cow()
		{
			wrapper = new concrete_wrapper<T>();
			converter = new root_converter<T>();
		}
		cow(cow const& other)
			: wrapper(other.cloneWrapper())
			, converter(other.cloneConverter())
		{
		}
		~cow()
		{
			delete converter;
			delete wrapper;
		}
		template<typename Other>
		cow(cow<Other> const& other)
			: wrapper(other.cloneWrapper())
			, converter(new proposed::converter<T, Other>(other.cloneConverter()))
		{
		}

		abstract_wrapper* cloneWrapper() const { return wrapper->clone(); }
		converter_base<T>* cloneConverter() const { return converter->clone(); }

		T* operator -> () const { return &converter->get(wrapper); }
		T& operator * () const { return converter->get(wrapper); }
	};

} //namespace proposed

struct Base
{
	virtual std::string get() =0;
};
struct Dev : Base
{
	std::string get() { return a; }
	std::string a;
};

int main()
{
	using namespace proposed;

	cow<Dev> d;
	d->a = "Hallo";
	cow<Base> b(d);
	std::cout << b->get() << std::endl;
}

valgrind didn't report any memory leaks on my machine for this sample
program. I also had a namespace "given" which I left out now. It
contained the implementation of the class diagram you posted. I hope you
don't mind the namespace "proposed" here.

HTH,
Frank

--
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply Frank 4/21/2009 8:21:49 PM

On 22 Apr., 04:21, Frank Birbacher <bloodymir.c...@gmx.net> wrote:
> SG schrieb:
>
> > Admittedly, it's a rather ugly "solution".  But so far I wasn't able
> > to come up with a less ugly implementation that meets all the design
> > goals.  It's more of a puzzle right now.  I'm aware that it may not be
> > very useful in practise.
>
> Ok, I was able to clone all information and not rely on object layout. I
> introduced a new class hierachy of converter objects, that implement a
> function from abstract_wrapper* to T& by means of a simply linked list
> of converters. Every time a cow<T> is constructed from a cow<U> a
> converter from U& to T& is instantiated and added to the list. Then you
> can retrieve the cloned T& from the new instance of abstract_wrapper.
>
> The downside is the long execution path on converting cow<T>. Memory
> allocation can be reduced, I think.

Thanks for your contribution. You have to admit, though, that the
downside you mentioned is a rather big issue and something that should
not be necessary. (But it seems it IS necessary to avoid making
assumptions about the consistency of memory layouts of complete
objects).

Cheers!
SG


-- 
      [ See http://www.gotw.ca/resources/clcm.htm for info about ]
      [ comp.lang.c++.moderated.    First time posters: Do this! ]

0
Reply SG 4/28/2009 6:06:09 AM

18 Replies
79 Views

(page loaded in 0.149 seconds)


Reply: