Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Placement new based on template sizeof()

Is this legal in c++11? Compiles with the latest intel compiler and appears to work, but I just get that feeling that it is a fluke.

 class cbase
        {
        virtual void call();
        };

template<typename T> class functor : public cbase
    {
    public:
        functor(T* obj, void (T::*pfunc)())
            : _obj(obj), _pfunc(pfunc) {}

        virtual void call()
            {
            (_obj)(*_pfunc)();
            }
    private:
        T& _obj;
        void (T::*_pfunc)();            
        //edited: this is no good:
        //const static int size = sizeof(_obj) + sizeof(_pfunc);
    };

class signal
    {
    public:
        template<typename T> void connect(T& obj, void (T::*pfunc)())
            {
            _ptr = new (space) functor<T>(obj, pfunc);
            }
    private:
        cbase* _ptr;
        class _generic_object {};
        typename aligned_storage<sizeof(functor<_generic_object>), 
            alignment_of<functor<_generic_object>>::value>::type space;
        //edited: this is no good:
        //void* space[(c1<_generic_object>::size / sizeof(void*))];

    };

Specifically I'm wondering if void* space[(c1<_generic_object>::size / sizeof(void*))]; is really going to give the correct size for c1's member objects (_obj and _pfunc). (It isn't).

EDIT: So after some more research it would seem that the following would be (more?) correct:

typename aligned_storage<sizeof(c1<_generic_object>), 
    alignment_of<c1<_generic_object>>::value>::type space;

However upon inspecting the generated assembly, using placement new with this space seems to inhibit the compiler from optimizing away the call to 'new' (which seemed to happen while using just regular '_ptr = new c1;'

EDIT2: Changed the code to make intentions a little clearer.

like image 303
Chris Bigart Avatar asked Jun 08 '13 05:06

Chris Bigart


2 Answers

const static int size = sizeof(_obj) + sizeof(_pfunc); will give the sum of the sizes of the members, but that may not be the same as the size of the class containing those members. The compiler is free to insert padding between members or after the last member. As such, adding together the sizes of the members approximates the smallest that object could possibly be, but doesn't necessarily give the size of an object with those members.

In fact, the size of an object can vary depending not only on the types of its members, but also on their order. For example:

struct A { 
    int a;
    char b;
};

vs:

struct B { 
    char b;
    int a;
};

In many cases, A will be smaller than B. In A, there will typically be no padding between a and b, but in B, there will often be some padding (e.g., with a 4-byte int, there will often be 3 bytes of padding between b and a).

As such, your space may not contain enough...space to hold the object you're trying to create there in init.

like image 106
Jerry Coffin Avatar answered Oct 24 '22 13:10

Jerry Coffin


I think you just got lucky; Jerry's answer points out that there may be padding issues. What I think you have is a non-virtual class (i.e., no vtable), with essentially two pointers (under the hood).

That aside, the arithmetic: (c1<_generic_object>::size / sizeof(void*)) is flawed because it will truncate if size is not a multiple of sizeof(void *). You would need something like:

((c1<_generic_object>::size + sizeof(void *) - 1) / sizeof(void *))

like image 21
Brett Hale Avatar answered Oct 24 '22 13:10

Brett Hale