2015-04-05 12 views
2

Я пытаюсь создать шаблонный класс, который содержит указатель на экземпляр произвольного класса и функции следующим образом:являются фиксированными размерами указателей и reinterpret_cast?

template<class C> 
class A { 
    typedef void (C::*FunctPtr)(); //e.g. void C::some_funct(); 

    FunctPtr functPtr_; 
    C* instPtr_; 
public: 
    A(FunctPtr functPtr, C* instPtr) 
     : functPtr_(functPtr) 
     , instPtr_(instPtr) {} 
}; 

Однако, я хочу, чтобы иметь возможность создавать экземпляры этого класса без динамического выделения памяти с помощью размещения новый. Стандарт C++ гарантирует, что этот класс шаблона имеет фиксированный размер для всех классов C?

В Don Clugston's article на указателях Я заметил диаграмму различных размеров для указателей функций-членов на разных компиляторах, а несколько компиляторов не всегда одинакового размера. Я думал, что я был укушен, но соответствует ли это стандартам? Из стандарта C++ sec. 5.2.10 на переинтерпретировать отливать:

- преобразование prvalue типа «указатель на функцию-член» на другой указатель на функцию-член типа и обратно к своему первоначальному типу дает первоначальный указатель на значение члена.

Будет ли эта инструкция из стандарта C++ указывать указатели на функции-члены одинакового размера?

Если нет, я полагаю, я все еще мог переписать код следующим образом, чтобы воспользоваться этой reinterpret_cast гарантии явно:

class GenericClass; 

template<class C> 
class A { 

    typedef void (GenericClass::*GenFunctPtr)(); 
    typedef void (C::*SpecificFunctPtr)(); 

    GenFunctPtr functPtr_; //store any kind of function ptr in this fixed format 
    GenericClass* instPtr_; 

public: 
    A(SpecificFunctPtr functPtr, C* instPtr) 
     : functPtr_(reinterpret_cast<GenFunctPtr>(functPtr)) 
     , instPtr_(reinterpret_cast<GenericClass*>(instPtr)) {} 

    void DoSomething() 
    { 
     //now convert pointers back to the original type to use... 
     reinterpret_cast<SpecificFunctPtr>(functPtr_); 
     reinterpret_cast<C*>(instPtr_); 
    } 
}; 

Теперь это, казалось бы, должны быть одинакового размера, и все же быть соответствуют стандартам , правильно? Я предпочел бы первый вариант, однако, если я тоже должен работать 2-й. Мысли?

+0

Стандарт даже не гарантирует, что 'C *' имеет одинаковый размер для всех 'C' (хотя он находится на обязательных платформах) - хотя он гарантирует, что вы можете совершить кругосветное путешествие через' void * '. Я также думаю, что это позволяет изменять любые вставленные дополнения. Если вам нужна полная мобильность, я думаю, вам не повезло - хотя я думаю, что ваш механизм будет работать на большинстве платформ. –

ответ

0

Я не знаю, указаны ли детали реализации указателя-к-члену в стандарте (не могу найти его), но поскольку мы знаем, что C* будут иметь одинаковый размер для всех C, мы можем просто определить размер FunctPtr для различных типов C и просто добавить static_assert:

template <class C> 
class A { 
    typedef void (C::*FunctPtr)(); //e.g. void C::some_funct(); 
    static_assert(sizeof(FunctPtr) == 16, 
     "unexpected FunctPtr size"); // from coliru, clang and gcc 

    FunctPtr functPtr_; 
    C* instPtr_; 
    ... 
}; 

по крайней мере я попытался это с несколькими типами классов ({базовых, производные, несколько производных} х {виртуальный, невиртуальное}) и он всегда давал одинаковый размер.

Это, вероятно, платформа и/или компилятор конкретных, а Pointers to member functions are very strange animals указывает:

Размер указателя на член-функции класса, который использует только одиночное наследование только размер указателя ,
Размер функции-указателя-члена класса, использующего множественное наследование, - это размер указателя плюс размер size_t.

Это не то, что я вижу в clang или gcc.

+0

Использование static_assert было бы хорошей двойной проверкой, вероятно, выполненной в месте размещения new, чтобы убедиться, что экземпляр будет соответствовать. Тем не менее, я более согласован, если код соответствует стандартам и гарантированно переносится, поскольку я уверен, что он совместим с конкретным компилятором. – Houe

0

Компиляторы Microsoft используют разные размеры указателей элементов в зависимости от того, насколько сложным является класс. Технически это не соответствует требованиям (поскольку вы можете определить указатели на члены, даже если определение класса не видно), но на практике он достаточно хорошо работает, что делает это по умолчанию. Для управления этим поведением доступны переключатели Pragmas и компилятора. См., Например, https://msdn.microsoft.com/en-us/library/83cch5a6.aspx.

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

+0

Компиляторы Microsoft, как известно, не соответствуют большинству стандартов. Но в отношении вашего второго утверждения: «... у разных классов могут быть указатели разных размеров для членов». Это не похоже на стандартную совместимость, так как в стандарте говорится, что вы можете использовать reinterpret_cast, отбрасывать назад и вперед между указателями элементов, даже если это относится к различным типам классов. – Houe

+0

Я полностью согласен. Я просто указываю компромисс, который делает один популярный компилятор - несовместимый, но более эффективный по умолчанию. Я подозреваю, что ваш гениальный механизм «GenericClass» столкнется с трудностями. –