EBCO - why does it require inheritance?

Discussion in 'C++' started by Chris Fairles, Aug 15, 2007.

  1. I recently implemented c++0x variants of tuple to take advantage of
    ECBO and then implemented unique_ptr using a tuple to hold the pointer
    - deleter pair so that sizeof(unique_ptr<int>) == sizeof(int*).

    It then came to my attention that its a lot of hoops to jump through
    just to take advantage of ECBO. I'm wondering whats preventing
    something like :

    struct SomeEmptyType {void f(){}};

    struct A {
    SomeEmptyType t;
    int * i;
    };

    from having a sizeof(int*) and not sizeof(int*) + 1 + padding to align
    with next boundary?

    I know the standard says that "Complete objects and member subobjects
    of class type shall have nonzero size." and "Base class subobjects are
    not so constrained." but why? Is there some C-compatibility issue?

    I think "A a; reinterpret_cast<SomeEmptyType*>(&a)->f();" could still
    be well formed even if t had no storage or is this not possible to do
    for compiler implementors?
     
    Chris Fairles, Aug 15, 2007
    #1
    1. Advertising

  2. Chris Fairles

    jg Guest

    On Aug 15, 10:30 am, Chris Fairles <> wrote:

    > just to take advantage of ECBO. I'm wondering whats preventing
    > something like :
    >
    > struct SomeEmptyType {void f(){}};
    >
    > struct A {
    > SomeEmptyType t;
    > int * i;
    >
    > };
    >
    > from having a sizeof(int*) and not sizeof(int*) + 1 + padding to align
    > with next boundary?


    t is 1 byte and the total is 8 bytes (3 bytes padding). It cannot be
    4 bytes.

    >
    > I know the standard says that "Complete objects and member subobjects
    > of class type shall have nonzero size." and "Base class subobjects are
    > not so constrained." but why? Is there some C-compatibility issue?


    Your example isn't about inheritance (class A : public B {} is). So
    the above statement does not apply to your example.


    JG
     
    jg, Aug 15, 2007
    #2
    1. Advertising

  3. Chris Fairles

    Joe Gottman Guest

    jg wrote:
    > On Aug 15, 10:30 am, Chris Fairles <> wrote:
    >
    >> just to take advantage of ECBO. I'm wondering whats preventing
    >> something like :
    >>
    >> struct SomeEmptyType {void f(){}};
    >>
    >> struct A {
    >> SomeEmptyType t;
    >> int * i;
    >>
    >> };
    >>
    >> from having a sizeof(int*) and not sizeof(int*) + 1 + padding to align
    >> with next boundary?

    >
    > t is 1 byte and the total is 8 bytes (3 bytes padding). It cannot be
    > 4 bytes.
    >
    >> I know the standard says that "Complete objects and member subobjects
    >> of class type shall have nonzero size." and "Base class subobjects are
    >> not so constrained." but why? Is there some C-compatibility issue?

    >



    I think it has to do with subobjects having unique addresses. Let's
    change your example to
    struct A {
    SomeEmptyType t1;
    SomeEmptyType t2;
    int *1;
    }

    Then if we create an object a of type A, we would have to have &a.t1
    != &a.t2, and similarly with member-pointers.

    Joe Gottman
     
    Joe Gottman, Aug 16, 2007
    #3
  4. On Aug 15, 7:57 pm, Joe Gottman <> wrote:
    > jg wrote:
    > > On Aug 15, 10:30 am, Chris Fairles <> wrote:

    >
    > >> just to take advantage of ECBO. I'm wondering whats preventing
    > >> something like :

    >
    > >> struct SomeEmptyType {void f(){}};

    >
    > >> struct A {
    > >> SomeEmptyType t;
    > >> int * i;

    >
    > >> };

    >
    > >> from having a sizeof(int*) and not sizeof(int*) + 1 + padding to align
    > >> with next boundary?

    >
    > > t is 1 byte and the total is 8 bytes (3 bytes padding). It cannot be
    > > 4 bytes.

    >
    > >> I know the standard says that "Complete objects and member subobjects
    > >> of class type shall have nonzero size." and "Base class subobjects are
    > >> not so constrained." but why? Is there some C-compatibility issue?

    >
    > I think it has to do with subobjects having unique addresses. Let's
    > change your example to
    > struct A {
    > SomeEmptyType t1;
    > SomeEmptyType t2;
    > int *1;
    >
    > }
    >
    > Then if we create an object a of type A, we would have to have &a.t1
    > != &a.t2, and similarly with member-pointers.
    >
    > Joe Gottman


    Ok, but if SomeEmptyType has no state, can you think of a use-case
    that requires t1 and t2 to have unique addresses? Otherwise isn't
    accessing t1 functionally equivolent to accessing t2 (and
    substitutable in all cases?).

    I can see if you were using the address of the object as its state,
    then yes, they have to be unqiue but I can't think of a case where I'd
    need that behavior.

    Chris
     
    Chris Fairles, Aug 21, 2007
    #4
  5. Chris Fairles

    Joe Greer Guest

    Chris Fairles <> wrote in
    news::
    >
    > Ok, but if SomeEmptyType has no state, can you think of a use-case
    > that requires t1 and t2 to have unique addresses? Otherwise isn't
    > accessing t1 functionally equivolent to accessing t2 (and
    > substitutable in all cases?).
    >
    > I can see if you were using the address of the object as its state,
    > then yes, they have to be unqiue but I can't think of a case where I'd
    > need that behavior.
    >


    One can turn that around a bit and ask why you need a stateless,
    non-virtual class in the first place? In my book that's called
    a namespace with free functions. No added overhead whatsoever.

    But, as to your question, each object declared in a class/struct is
    considered a separate instance of that class. Make sense so far? This
    means that while the values may be the same, the object identities are not.
    In C++, object identity is spelled &member. Therefore if &t1 == &t2, that
    would mean that the two object instances were the same object and they are
    not. They just have the same value. Make sense?

    joe
     
    Joe Greer, Aug 21, 2007
    #5
  6. Chris Fairles

    Joe Greer Guest

    Chris Fairles <> wrote in
    news::

    >>
    >> I think it has to do with subobjects having unique addresses.
    >> Let's
    >> change your example to
    >> struct A {
    >> SomeEmptyType t1;
    >> SomeEmptyType t2;
    >> int *1;
    >>
    >> }
    >>
    >> Then if we create an object a of type A, we would have to have
    >> &a.t1
    >> != &a.t2, and similarly with member-pointers.
    >>
    >> Joe Gottman

    >
    > Ok, but if SomeEmptyType has no state, can you think of a use-case
    > that requires t1 and t2 to have unique addresses? Otherwise isn't
    > accessing t1 functionally equivolent to accessing t2 (and
    > substitutable in all cases?).
    >
    > I can see if you were using the address of the object as its state,
    > then yes, they have to be unqiue but I can't think of a case where I'd
    > need that behavior.
    >


    Well, if SomeEmptyType is stateless and isn't some abstract base class,
    then it is best spelled 'namespace' in my opinion. Then you aren't
    creating an object for no purpose.

    Creating objects is the crux of your problem. When you declare a t1 and
    a t2, you are declaring to separate object instances. These instances
    have the same value, but are not the same object. In normal object-
    oriented terms, you would say that they didn't have the same object
    identity. That is, if I declared two ints i and j. I assigned both of
    them the value 1. We would then say that i == j, but we would not say
    that i and j were the same int. Same thing here. We declared t1 and
    t2. We might possibly say that t1 == t2 (because they were both empty)
    but we would not say that t1 and t2 were the same object. Clear so far?
    In C++ object identity is determined by its address. Therefore if t1
    and t2 were to maintain separate identities &t1 != &t2. This is a
    fundamental property of object oriented programming and not lightly
    broken especially if there is so easy a way to get what you want without
    breaking it.

    So, we change the code to:

    namespace SomeEmptyType
    {
    void f();
    }

    struct A {
    int *i;
    }

    using namespace SomeEmptyType;

    f();

    -or-

    class SomeEmptyType
    {
    public:
    static void f();
    };

    struct A {
    int *i;
    }

    SomeEmptyType::f();


    and we are in the same boat as before. There is nothing to be gained in
    accessing f() via an instance of A, so why tie them together?

    joe
     
    Joe Greer, Aug 21, 2007
    #6
  7. On Aug 21, 9:27 am, Joe Greer <> wrote:
    > Chris Fairles <> wrote innews::
    >
    >
    >
    > > Ok, but if SomeEmptyType has no state, can you think of a use-case
    > > that requires t1 and t2 to have unique addresses? Otherwise isn't
    > > accessing t1 functionally equivolent to accessing t2 (and
    > > substitutable in all cases?).

    >
    > > I can see if you were using the address of the object as its state,
    > > then yes, they have to be unqiue but I can't think of a case where I'd
    > > need that behavior.

    >
    > One can turn that around a bit and ask why you need a stateless,
    > non-virtual class in the first place? In my book that's called
    > a namespace with free functions. No added overhead whatsoever.
    >
    > But, as to your question, each object declared in a class/struct is
    > considered a separate instance of that class. Make sense so far? This
    > means that while the values may be the same, the object identities are not.
    > In C++, object identity is spelled &member. Therefore if &t1 == &t2, that
    > would mean that the two object instances were the same object and they are
    > not. They just have the same value. Make sense?
    >
    > joe

    This all came about while implementing unique_ptr for c++0x.

    template <class _Tp>
    struct default_delete : public unary_function<_Tp*,void> {
    default_delete() {}
    template <class _Up> default_delete(const default_delete<_Up>&) {}
    void operator()(_Tp* __ptr) const {
    static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete
    type");
    delete __ptr;
    }
    };

    template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    class unique_ptr {
    /* ... */

    _Tp* __ptr;
    _Tp_Deleter __deleter;
    };

    However, I wanted sizeof(unique_ptr<int>) == sizeof(int*) which
    requries EBCO applied somehow. What I did was implement EBCO for
    tuples and store the pair as tuple<_Tp*,_Tp_Deleter> but I started
    wondering why isn't it possible for compilers to determine that
    __deleter is actually empty, its address is never taken (perhaps
    theres no realiable way to determine this, I'm not a compiler writer
    heh), so don't allocate storage for it.

    I just couldn't come up with a good argument (besides quoting the
    standard) when someone looks at the required code to get ebco and
    claims "C++ is stupid. Why do you have to jump through hoops and re-
    design your class just to not waste 4/8 bytes required to store an
    empty, stateless object. You need convoluted and non-obvious solutions
    to a seemingly trivial problem."

    Cheers,
    Chris
     
    Chris Fairles, Aug 21, 2007
    #7
  8. Chris Fairles

    Joe Greer Guest

    Chris Fairles <> wrote in
    news::

    > This all came about while implementing unique_ptr for c++0x.
    >
    > template <class _Tp>
    > struct default_delete : public unary_function<_Tp*,void> {
    > default_delete() {}
    > template <class _Up> default_delete(const default_delete<_Up>&) {}
    > void operator()(_Tp* __ptr) const {
    > static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete
    > type");
    > delete __ptr;
    > }
    > };
    >
    > template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    > class unique_ptr {
    > /* ... */
    >
    > _Tp* __ptr;
    > _Tp_Deleter __deleter;
    > };
    >


    Given the nature of your deleter's, you can either make the method static like:

    template <class _Tp>
    struct default_delete : public unary_function<_Tp*,void> {
    default_delete() {} template <class _Up> default_delete(const default_delete
    _Up>&) {}
    static void delete(_Tp* __ptr) const {
    static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete
    type");
    delete __ptr;
    }
    };

    template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    class unique_ptr {
    /* ... */

    _Tp* __ptr;
    ~unique_ptr() { _Tp_Deleter::delete(__ptr); }
    };

    or you can instatiate it when you need it rather than at construction time.

    template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    class unique_ptr {
    /* ... */

    _Tp* __ptr;
    ~unique_ptr() { _Tp_Deleter()(__ptr); }
    };


    Just some thoughts,
    joe
     
    Joe Greer, Aug 22, 2007
    #8
  9. On Aug 22, 8:17 am, Joe Greer <> wrote:
    > Chris Fairles <> wrote innews::
    >
    >
    >
    > > This all came about while implementing unique_ptr for c++0x.

    >
    > > template <class _Tp>
    > > struct default_delete : public unary_function<_Tp*,void> {
    > > default_delete() {}
    > > template <class _Up> default_delete(const default_delete<_Up>&) {}
    > > void operator()(_Tp* __ptr) const {
    > > static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete
    > > type");
    > > delete __ptr;
    > > }
    > > };

    >
    > > template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    > > class unique_ptr {
    > > /* ... */

    >
    > > _Tp* __ptr;
    > > _Tp_Deleter __deleter;
    > > };

    >
    > Given the nature of your deleter's, you can either make the method static like:
    >
    > template <class _Tp>
    > struct default_delete : public unary_function<_Tp*,void> {
    > default_delete() {} template <class _Up> default_delete(const default_delete
    > _Up>&) {}
    > static void delete(_Tp* __ptr) const {
    > static_assert(sizeof(_Tp) > 0, "can't delete pointer to incomplete
    > type");
    > delete __ptr;
    > }
    > };
    >
    > template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    > class unique_ptr {
    > /* ... */
    >
    > _Tp* __ptr;
    > ~unique_ptr() { _Tp_Deleter::delete(__ptr); }
    > };
    >


    A good idea, but I don't think this will work when considering user-
    defined deleters. It would require deleters to have a static function
    but the standard allows the deleter to have non-static state that can
    be accessed within the member func (operator or not) that does the
    delete.

    > or you can instatiate it when you need it rather than at construction time.
    >
    > template <class _Tp, class _Tp_Deleter = default_delete<_Tp>>
    > class unique_ptr {
    > /* ... */
    >
    > _Tp* __ptr;
    > ~unique_ptr() { _Tp_Deleter()(__ptr); }
    > };
    >


    Again, due to deleters having state, and unique_ptr's being able to
    store references to deleters, the following is well formed:

    struct deleter
    {
    deleter(std::string msg):m(msg){}
    void operator()(int *) {std::cout << msg; delete i;}
    std::string m;
    };
    deleter d("hi mom");
    std::unique_ptr<int,deleter&>(new int, d);
    //or std::unique_ptr<int,deleter>(new int, deleter("hi mom")) etc.

    So instantiation-on-use won't work so long as the above is allowed.

    > Just some thoughts,


    Much appreciated.

    Chris
     
    Chris Fairles, Sep 8, 2007
    #9
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Herman Eldering

    Re: Why does using keyword require a new scope?

    Herman Eldering, Aug 24, 2003, in forum: ASP .Net
    Replies:
    0
    Views:
    984
    Herman Eldering
    Aug 24, 2003
  2. Jip from Paris
    Replies:
    0
    Views:
    1,914
    Jip from Paris
    Aug 25, 2003
  3. Dave

    EBCO and inheritance

    Dave, Apr 24, 2004, in forum: C++
    Replies:
    3
    Views:
    416
    Rob Williscroft
    Apr 25, 2004
  4. Mr. SweatyFinger

    why why why why why

    Mr. SweatyFinger, Nov 28, 2006, in forum: ASP .Net
    Replies:
    4
    Views:
    990
    Mark Rae
    Dec 21, 2006
  5. Mr. SweatyFinger
    Replies:
    2
    Views:
    2,256
    Smokey Grindel
    Dec 2, 2006
Loading...

Share This Page