Снижение C ++ до производного класса на основе переменной - PullRequest
5 голосов
/ 24 июня 2011

Предположим, у меня есть базовый класс "Shape" и производные классы "Triangle", "Square" и "Circle". Член "Shape" является int "shapeType".

Если shapeType == 1, то это треугольник. Если shapeType == 2, то это квадрат. Если shapeType == 3, то это круг.

Мне интересно знать, учитывая, что у меня есть только объект "Shape", который когда-то был производным объектом, если есть способ "динамического" приведения к надлежащему производному классу с использованием значения shapeType.

Я знаю, что могу сделать оператор переключения жесткого кода, примерно как:

Triangle* t;
Square* s;
Circle* c;

switch (shape->shapeType) {
case 1:
   t = (Triangle*)shape;
case 2: 
   ...
}

Однако вышеизложенное требует, чтобы я указывал на КАЖДУЮ возможность производного класса. Мне интересно, есть ли способ сделать это без жесткого кодирования каждого класса, а вместо этого как-то определить карту типа класса, где ключом является shapeType, а значением является тип класса.

Ответы [ 4 ]

10 голосов
/ 24 июня 2011

Если у них есть виртуальные функции, используйте dynamic_cast:

t = dynamic_cast<Triangle*>(shape);
if ( t )
{
     //use t
}

Но обратите внимание: вы должны попытаться определить классы и виртуальные функции таким образом, чтобы вам вряд ли понадобилось использовать dynamic_cast. Предпочитаю четко определенный интерфейс и полиморфизм в целом.

Вот один пример,

class Shape
{
   public:
     virtual ~Shape() {} //destructor must be virtual - important!
     virtual double Area() const = 0;
};

class Triangle : public Shape
{
   public:
     Triangle(double a, double b, double c);
     virtual double Area() const 
     {
         //calculate area and return it!
     }
};

Shape *s = new Triangle(10, 20, 30);
double aread = s->Area(); //calls Triangle::Area()

Нет необходимости использовать shapeType переменную.

3 голосов
/ 24 июня 2011

dynamic_cast является ответом на вашу проблему.

Описание

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

void foo(Shape * p_shape)
{
   Triangle * t = dynamic_cast<Triangle *>(p_shape) ;

   // if p_shape is a triangle, or derives from triangle,
   // then t is non-NULL, and you can use it
}

Дело в том, что t будет ненулевым, даже если p_shape не является в точности треугольником, но все еще наследует треугольник. Например, в случае:

Shape
 |
 +-- Square
 |
 +-- Triangle
      |
      +-- EquilateralTriangle
      |
      +-- RectangleTriangle

если форма представляет собой треугольник, или равносторонний треугольник, или прямоугольник, то t не будет равен NULL, что намного мощнее, чем ваше первоначальное решение пометить точный тип с помощью константного числа.

Обратите внимание, что для dynamic_cast для работы с классом в этом классе должен быть хотя бы виртуальный метод (, который обычно имеет место в иерархии наследования деревьев, dynamic_cast используется в )

Метание dynamic_cast

Вместо использования указателей вы можете использовать ссылки, но со ссылками, поскольку dynamic_cast не может вернуть «ошибочную ссылку», он выдаст std::bad_cast, который вы можете поймать при необходимости:

void foo(Shape & p_shape)
{
   Triangle & t = dynamic_cast<Triangle &>(p_shape) ;

   // if p_shape is a triangle, or derives from triangle,
   // then the dynamic_cast succeeds.
   // If not, a std::bad_cast is thrown
}

dynamic_cast злоупотреблять?

Следует отметить, что динамическое приведение без броска на основе указателя может привести к появлению кода, подобного переключателю (, но если вы не можете полагаться на виртуальные методы, вам придется «включать типы»). .. ):

void foo(Shape * p_shape)
{
   if(Triangle * t = dynamic_cast<Triangle *>(p_shape))
   {
      // if p_shape is a triangle, then t is non-NULL,
      // and you can use it
   }
   else if(Square * s = dynamic_cast<Square *>(p_shape))
   {
      // if p_shape is a square, then t is non-NULL
      // and you can use it
   }
   // etc...

Как и весь код «включения типов», он подвержен ошибкам ( что, если вы забудете обработать тип? ), но иногда этого не избежать, поэтому стоит упомянуть.

(В качестве бонуса любопытства, IIRC, нотация if(type * p = ...) была сначала добавлена ​​в C ++, чтобы обработать этот случай и сделать код менее многословным ... Если я что-то не пропустил, эта нотация не разрешена в C #)

RTTI

В целом, dynamic_cast опирается на RTTI (информацию о типе RunTime), которую иногда можно отключить ( на работе, пока несколько лет назад «технические эксперты» не решили, что в этом нет необходимости). и, следовательно, обязательно быть отключенным в наших сборках ... Аааа, "C-with классы экспертов" ... )

Не позволяйте себе быть вовлеченным в войну C против C ++: если вы не работаете в очень стесненной среде (то есть во встроенной разработке), RTTI (как и все другие функции C ++, такие как обработка исключений) должен быть активирован.

Более подробная информация о RTTI: http://www.cplusplus.com/reference/std/typeinfo/

И, возможно, мой вопрос переполнения стека по RTTI вас заинтересует: Возможные примеры C ++ RTTI

2 голосов
/ 24 июня 2011

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

Если вы действительно должны быть удручены, как это используйте dynamic_cast.

0 голосов
/ 24 июня 2011

Используйте динамическое приведение.http://en.wikipedia.org/wiki/Dynamic_cast

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