C++ reinterpret_cast для производного класса

Родительский класс:

template <class T>
class Point
{
    protected

        T x;
        T y;

};

Производный класс:

template <class T>
class Point3DTopo: public Point <T>
{
    protected:

        T z;
        Face <T> *face;   //Points to any face
};

Я хотел бы привести один объект класса PointsList к другому объекту Points3DTopoList (и наоборот), где:

template <class T>
class PointsList
{
  protected:
         std::vector <Point <T> *> points;  //Only illustration, not possible with   templaes
};


template <class T>
class Points3DTopoList
{
  protected:
         std::vector <Point3DTopo <T> *> points;  //Only illustration, not possible with   templaes
};

Допустимо ли такое преобразование?

Points3DTopoList <T> *pl = new Points3DTopoList <T> ();
...
PointsList <T> *pl = reinterpret_cast < PointsList <T> * > ( pl3D );

А обратное преобразование?

PointsTopoList <T> *pl = new PointsTopoList <T> ();
...
Points3DTopoList <T> *pl3D = reinterpret_cast < Points3DTopoList <T> * > ( pl );

Указатель Face каждого Point3Topo будет инициализирован NULL или будет неопределенным?


person Woody    schedule 18.01.2011    source источник
comment
Опять таки? Позвольте мне повторить: это ужасная идея, не делайте этого. Вам не нужно reinterpret_cast, вам просто нужно потратить время на то, чтобы преобразовать один контейнер в другой по пунктам. Вот и все. Здесь нет никаких трюков, они вам не нужны, просто простой цикл. И снова прекратите динамически распределять все подряд!   -  person GManNickG    schedule 18.01.2011
comment
Кроме всего прочего, вы должны использовать dynamic_cast   -  person    schedule 18.01.2011


Ответы (5)


Такой бросок не допускается. Это фундаментальная проблема: вам либо нужно конвертировать через копирование, либо адаптировать ваши определения классов так, чтобы у вас был ListOf<PointT, T>, т.е. параметризованный как по типу точки, так и по типу внутри точки.

Однако дизайн класса в любом случае имеет недостатки: вы не должны выводить Point3D из Point, это нарушает принцип подстановки Лисков (LSP) – или, в более общем смысле, трехмерная точка не является двухмерной точкой. На самом деле, как раз наоборот: 2D-точка — это частный случай 3D-точки.

Итак, если вы хотите иметь здесь наследование, оно должно идти другим путем (т. е. 2D наследуется от 3D), но это, скорее всего, также нарушает LSP и очень неудобно (так как тогда ваша 2D-точка будет иметь избыточную переменную, которая всегда будет фиксированной). Проще говоря, между 2D и 3D точками нет подходящего отношения наследования, это разные объекты.

person Konrad Rudolph    schedule 18.01.2011
comment
Приведение разрешено, но существуют серьезные ограничения на то, что вы можете делать с результатом. - person Marcelo Cantos; 18.01.2011
comment
Он отлично работает для получения Point3D из Point2D, без проблем. Это может не соответствовать концептуальному математическому представлению, но это тоже не проблема. Однако, когда рассматриваются коллекции (указатели) таких объектов, вы сталкиваетесь с проблемой, где, например. Java реагирует, вызывая исключение: если набор Point2D считается набором Point3D, он может содержать чистый Point2D. В C++ этого можно избежать с помощью константности и интерфейсов. Но это связано, и, учитывая продолжительность дебатов об Эллипсе/Круге, я не должен был упоминать об этом, извините. ;-) - person Cheers and hth. - Alf; 18.01.2011

Почти единственное, что гарантирует reinterpret_cast, — это то, что приведение от A* к B*, а затем обратно к A* дает исходный указатель. Использование промежуточного значения B* для чего-либо, кроме приведения обратно к A*, не определено.

person Marcelo Cantos    schedule 18.01.2011
comment
reinterpret_cast также гарантирует приведение от первого члена POD к типу POD и наоборот. это в начале раздела классов. если бы Йоханнес не спал, он, вероятно, также указал бы на дефект в c++98, где reinterpret_cast to/from void* формально недействителен, исправлен в c++0x (но это не имеет значения, насколько мне известно, ни один компилятор не запрещает reinterpret_cast to/from void* ) - person Cheers and hth. - Alf; 18.01.2011
comment
Хороший улов, @Alf. Я немного уточнил свой ответ. - person Marcelo Cantos; 18.01.2011

Это просто неопределенное поведение в обоих направлениях.

person Cheers and hth. - Alf    schedule 18.01.2011
comment
Разрешено использование reinterpret_cast между несвязанными типами. То, что вы делаете с результатом, может привести к неприятностям. - person Marcelo Cantos; 18.01.2011

Было бы разумно обеспечить преобразование из Points3DTopoList в PointsList, учитывая, что Point3DTopo является производным от Point. Наследование, которое обеспечивает это преобразование автоматически, возможно, но я подозреваю, что ваши требования к общедоступному интерфейсу (опущенные в вопросе) делают его скорее хлопотным, чем активом.

Пример предоставления пути конверсии:

template<class T>
struct PointsList {
  // Points3DTopoList needs a way to construct a PointsList
  template<class Iter>
  PointsList(Iter begin, Iter end)
  : points(begin, end)
  {}

private:
  std::vector<Point<T> > points;
};

template<class T>
struct Points3DTopoList {

  operator PointsList<T>() const {
    return PointsList<T>(points.begin(), points.end());
  }

  PointsList<T> to_points_list() const {
    return PointsList<T>(points.begin(), points.end());
  }

private:
  std::vector<Point3DTopo<T> > points;
};

Это обеспечивает два пути конверсии — обычно вы выбираете один и не предоставляете другой. Оператор преобразования является неявным преобразованием (в C++0x вы можете пометить его как явное), в то время как именованный метод не является «преобразованием» с технической точки зрения (поэтому никогда не применяется для любого неявного или явного преобразования), а вызывается явно и использовал таким образом.

Вы также можете обеспечить явное преобразование с помощью явного конструктора в PointsList, который принимает Points3DTopoList, и это работает в текущем C++ за счет инвертирования отношения зависимости от того, как оно обычно лежит: то есть PointsList будет знать и заботиться о Points3DTopoList вместо этого. наоборот.

Тем не менее, может иметь смысл предоставить контейнер "generic-Point"; то есть тот, который принимает любой конкретный Point-подобный тип.

template<class Point>
struct GenericPointContainer {
private:
  std::vector<Point> points;
};

Самая большая сила здесь заключается в том, что методы GenericPointContainer могут использовать различные функции из производных классов Point, которых нет в самой Point, но которые все же могут быть созданы непосредственно в Point. Это работает, потому что не все методы создаются при создании экземпляра шаблона класса, и практическим примером является то, как std::reverse_iterator перегружает оператор +=, который работает только для итераторов с произвольным доступом, но может быть создан для итераторов без произвольного доступа, например, std::reverse_iterator‹std::list‹int>::iterator>.

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

typedef GenericPointContainer<Point<int> > PointsList;
typedef GenericPointContainer<Point3DTopoList<int> > Points3DTopoList;

С++ 0x действительно поможет вам здесь с определениями типов шаблонов (вы можете использовать повторную привязку в текущем С++, но это становится тупым); как вы можете видеть, мне пришлось указать T для typedefs, так что это не так часто, как в противном случае.

person Fred Nurk    schedule 18.01.2011

Мне уже ответили: Переосмысление C++?

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

ВАША СОБСТВЕННАЯ ФУНКЦИЯ CAST.

person SVGreg    schedule 18.01.2011
comment
Вы можете создать класс-оболочку для представления PointsTopoList из Points3DTopoList и другую оболочку для представления Points3DTopoList из PointsTopoList. В оболочке вы должны копировать указатели только на данные, а не копировать все данные. - person SVGreg; 18.01.2011
comment
Вы можете включить в класс Point все переменные данных (включая x, y, z и т. д.). Вы будете использовать этот класс для создания вектора в Points3DTopoList (std::vector ‹Point ‹T› *› points;) и того же в PointsTopoList. В этом случае вам не нужны никакие приведения. - person SVGreg; 18.01.2011
comment
Вы можете использовать любые другие хорошие идеи, но не идеи с неопределенным поведением. - person SVGreg; 18.01.2011