2013-06-08 3 views
5

Является ли это законным в C++ 11? Компилируется с последним компилятором Intel и, похоже, работает, но я просто чувствую, что это случайность.Размещение новое на основе template sizeof()

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*))]; 

    }; 

Конкретно я интересно, если void* space[(c1<_generic_object>::size/sizeof(void*))]; действительно собирается дать правильный размер для объектов-членов c1 в (_obj и _pfunc). (Это не так).

EDIT: Так после еще некоторые исследований, казалось бы, что следующий будет (больше?) Правильно:

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

Однако при проверке сгенерированной сборки, используя размещение нового с этим пространством, кажется, тормозит компилятор от оптимизации прочь призыв к «новым» (который, казалось, случилось в то время, используя только регулярные «_ptr = новый c1;»

EDIT2: Изменен код, чтобы сделать намерения немного понятнее

+0

Как это возможно даже для компиляции без предоставления инициализатора для ссылочного элемента в 'c1'? Есть ли какая-то особая важность в этом ссылочном элементе или нет? – AnT

+0

Кроме того, какова идея расчета объема памяти с помощью 'sizeof (_obj)', которая оценивает размер полного объекта (т. Е. 'Sizeof T'), но позже построит объект' c1 'в этом месте, что будет физически содержать только * reference * to 'some_type_t' (реализован как указатель)? – AnT

+0

Я не знаю, скомпилирован ли он или нет, это очень упрощенный пример, который я сделал для публикации здесь, чтобы вырезать соответствующие разделы. Я пишу класс сигнала/обратного вызова и хочу, если возможно, исключить распределение динамической памяти. –

ответ

3

.даст сумму размеров членов, но это может быть не такое же, как размер класса, содержащего эти элементы. Компилятор может вставлять дополнения между членами или после последнего элемента. Таким образом, сложение размеров членов приближается к наименьшему, возможно, объекту, но не обязательно дает размер объекта с этими членами.

Фактически размер объекта может варьироваться в зависимости не только от типов его элементов, но и от их порядка. Например:

struct A { 
    int a; 
    char b; 
}; 

против:

struct B { 
    char b; 
    int a; 
}; 

Во многих случаях A будет меньше B. В A обычно не будет отступов между a и b, но в B часто будет некоторое заполнение (например, с 4-байтовым int, часто бывает 3 байта заполнения между b и a).

Как таковой, ваш space может не содержать достаточно ... пространства для хранения объекта, который вы пытаетесь создать там, в init.

+0

Это хороший момент. В идеале я мог бы хранить объекты непосредственно в c2, но поскольку я не знаю T до тех пор, пока не будет вызвана функция-член, я не могу этого сделать. –

1

Думаю, вам просто повезло; Ответ Джерри указывает, что могут быть проблемы с заполнением. Я думаю, что у вас есть не виртуальный класс (т. Е. Нет vtable), по существу два указателя (под капотом).

Это в стороне, арифметика: (c1<_generic_object>::size/sizeof(void*)) является некорректной, поскольку она будет укоротить, если size является не кратна sizeof(void *). Вам нужно будет что-то вроде:

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

1

Этот код даже не добраться до вопросов заполнения, поскольку он имеет несколько более насущных из них.

Категория шаблона c1 определяется как элемент T &_obj ссылочного типа. Применение sizeof до _obj в объеме c1 будет оцениваться до размера T, а не по размеру элемента ссылки.Невозможно получить физический размер ссылки в C++ (по крайней мере, напрямую). Между тем, любой фактический объект типа c1<T> будет физически содержать ссылку на T, которая обычно реализуется в таких случаях, как указатель «под капотом».

По этой причине совершенно непонятно мне, почему значение c1<_generic_object>::size используются как мера памяти, необходимой для в-темпа строительства реального объекта типа c1<T> (для любого T). Это просто не имеет никакого смысла. Эти размеры не связаны вообще.

По чистой удаленности размер пустого класса _generic_object может оцениваться с тем же (или большим) значением как размер физической реализации ссылочного элемента. В этом случае код будет выделять достаточный объем памяти. Можно даже утверждать, что равенство sizeof(_generic_object) == sizeof(void *) будет «обычно» удерживаться на практике. Но это было бы абсолютно произвольным совпадением, без каких-либо значимых оснований.

Это даже похоже, что красная селедка намеренно вставлена ​​в код с целью чистой обфускации.

P.S. В GCC sizeof пустого класса фактически вычисляется до 1, а не для любого «выровненного» размера. Это означает, что вышеприведенный метод гарантированно инициализирует c1<_generic_object>::size со слишком маленьким значением. В частности, в 32-битном GCC значение c1<_generic_object>::size будет 9, тогда как фактический размер любого c1<some_type_t> будет 12 байтов.

Смежные вопросы