Это законно в C ++ 11? Компилируется с последним компилятором Intel и, кажется, работает, но я просто чувствую, что это случайность.
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). (Это не так).
РЕДАКТИРОВАТЬ:
Таким образом, после еще одного исследования может показаться, что следующее будет (более?) Правильным:
typename aligned_storage<sizeof(c1<_generic_object>),
alignment_of<c1<_generic_object>>::value>::type space;
Однако при проверке сгенерированной сборки использование размещения new с этим пространством, по-видимому, не позволяет компилятору оптимизировать вызов «new» (что, похоже, происходит при использовании обычного «_ptr = new c1;»).
РЕДАКТИРОВАТЬ 2: Изменен код, чтобы сделать намерения немного яснее.
const static int size = sizeof(_obj) + sizeof(_pfunc);
даст сумму размеров членов, но это может не совпадать с размером класса, содержащего эти члены. Компилятор может свободно вставлять отступ между членами или после последнего члена. Таким образом, сложение размеров элементов приблизительно соответствует наименьшему возможному объекту, но не обязательно дает размер объекта с этими элементами.
На самом деле, размер объекта может варьироваться в зависимости не только от типов его членов, но и от их порядка. Например:
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
,
Я думаю, тебе просто повезло; Ответ Джерри указывает на то, что могут быть проблемы с заполнением. Я думаю, что у вас есть не виртуальный класс (то есть, нет vtable), с двумя указателями (под капотом).
Что в стороне, арифметика: (c1<_generic_object>::size / sizeof(void*))
ущербен, потому что он будет обрезать, если size
является не кратный sizeof(void *)
, Вам нужно что-то вроде:
((c1<_generic_object>::size + sizeof(void *) - 1) / sizeof(void *))
Этот код даже не касается проблем с заполнением, потому что в нем есть несколько более непосредственных проблем.
Шаблон класса 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 *)
равенство будет «обычно» выполняться на практике. Но это было бы просто произвольным совпадением без какой-либо значимой основы.
Это даже выглядит как красная сельдь, намеренно вставленная в код с целью полного запутывания.
Постскриптум В GCC sizeof
пустого класса на самом деле оценивает 1
, а не к любому «выровненному» размеру. Это означает, что вышеуказанный метод гарантированно инициализируется c1<_generic_object>::size
со значением, которое слишком мало. Более конкретно, в 32-битном GCC значение c1<_generic_object>::size
будет 9
, в то время как фактический размер любого c1<some_type_t>
будет 12
байт.