c ++ reinterpret_cast, virtual и шаблоны хорошо? - PullRequest
2 голосов
/ 26 января 2012

В C ++ предположим следующую иерархию классов:

class BaseClass { };
class ChildClass : public BaseClass { };

Далее предположим, что фабричные классы для этих двух классов имеют общий базовый шаблонный шаблон:

template<typename T>
class Factory {
public:
  virtual T* create() = 0;
};

class BaseClassFactory : public Factory<BaseClass> {
public:
  virtual BaseClass* create() {
    return new BaseClass(&m_field);
  }
private:
  SomeClass m_field;
};

class ChildClassFactory : public Factory<ChildClass> {
public:
  virtual ChildClass* create() {
    return new ChildClass(&m_field);
  }
private:
  SomeOtherClass m_field; // Different class than SomeClass
};

Обратите внимание, чтоВнутренняя структура ChildClassFactory и BaseClassFactory отличается из-за их разных полей.

Теперь, если есть экземпляр ChildClassFactory (или Factory<ChildClass>), могу ли я безопасно привести его к Factory<BaseClass> (через reinterpret_cast)?

Factory<ChildClass>* childFactory = new ChildClassFactory();

// static_cast doesn't work - need to use reinterpret_cast
Factory<BaseClass>* baseFactory = reinterpret_cast<Factory<BaseClass>*>(childFactory);

// Does this work correctly? (i.e. is "cls" of type "ChildClass"?)
BaseClass* cls = baseFactory->create();

Я знаю, что вы не всегда можете кастовать шаблонные классы таким образом, но в этом особом случае приведение должно быть безопасным, не так ли?

Я протестировал его с Visual C ++ 2010, и он работает.Теперь у меня вопрос: переносимо ли это на другие компиляторы?

Обновление: Поскольку произошла путаница, позвольте мне еще кое-что прояснить (что должно быть) в моем примере:

  • ChildClass - это дочерний класс BaseClass
  • Пользователь Factory<BaseClass> не знает, какой дочерний класс BaseClass будет создан.Все, что он знает, это то, что BaseClass создано.
  • Factory<T> не имеет собственных полей (кроме vtable).
  • Factory::create() равно virtual

Ответы [ 3 ]

5 голосов
/ 26 января 2012

Нет, это не так.Вы не можете использовать результат reinterpret_cast, кроме как для приведения материала обратно, за исключением нескольких особых случаев:

ISO14882: 2011 (e) 5.2.10-7:

Указатель объекта может быть явно преобразован в указатель объекта другого типа. 70 Когда значение v типа «указатель на T1» преобразуется в тип «указатель на cv T2», результатом является static_cast (static_cast (v)) если и T1, и T2 являются типами стандартной компоновки (3.9) и требования к выравниванию T2 не являются более строгими, чем требования к T1, или если какой-либо из типов является недействительным.Преобразование значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов, а требования к выравниванию для T2 не более строгие, чем требования для T1) и обратно к исходному типу дает исходныйзначение указателяРезультат любого другого такого преобразования указателя не определен.

Чтобы сделать возможный сценарий сбоя более понятным, рассмотрите множественное наследование, где использование static_cast или dynamic_cast иногда корректирует значение указателя,но reinterpret_cast не будет.Рассмотрим приведение в этом примере значения от A* к B*:

struct A { int x; };
struct B { int y; };
struct C : A, B { };

Чтобы понять, как ваш код тоже дает сбой и по-другому, рассмотрим, как большинство компиляторов реализуют механизм вызова виртуальной функции: с помощью виртуальных указателей.Ваш экземпляр ChildClassFactory будет иметь виртуальный указатель, указывающий на виртуальную таблицу ChildClassFactory.Теперь, когда вы reinterpret_cast этот зверь, он просто случайно "работает", потому что компилятор ожидает некоторый виртуальный указатель, указывающий на виртуальную таблицу, которая будет иметь такую ​​же / подобную структуру.Но он все равно будет содержать значения, указывающие на виртуальные функции ChildCLassFactory, поэтому эти функции будут вызываться.Все это долго после , вызывающего неопределенное поведение.Это как если бы вы прыгали с машиной в большой каньон и думали: «Эй, все в порядке!» Только потому, что вы еще не упали.

0 голосов
/ 26 января 2012

Я поставил галочку в исходном ответе выше (чтобы отдать ему должное), но я решил подвести итог тому, что я здесь узнал.

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

Это означает, что структура (и) данных, которые внутренне используются для диспетчеризации виртуальных вызовов (например, vtables), могут или не могут быть бит-совместимыми для экземпляров шаблона, созданных из того же шаблона.

0 голосов
/ 26 января 2012

Нет, reinterpret_cast должен использоваться только для низкоуровневого кода, поскольку он не будет выполнять корректную манипуляцию с адресом.Вместо этого используйте static_cast или dynamic_cast,

. Почему вам нужны две фабрики, которые не вписываются в шаблон фабрики GoF.и не является хорошим дизайном ОО (вы хотите использовать полимофизму, встроенную в язык).

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

...