лучший способ, чем приведение из базового класса в производный класс - PullRequest
1 голос
/ 05 января 2009

Я знаю, что удручение не сработает. Мне нужен метод, который будет работать. Вот моя проблема: у меня есть несколько различных производных классов из базового класса. Моей первой попыткой было создать массив базового класса. Программа должна выбирать (более или менее случайным образом) различные производные классы. Я пробовал приводить базовый класс к производному классу, помещая его в массив базового класса, но, очевидно, это не сработало. Я искренне надеялся на другой метод, кроме простого прикрепления массивов всех производных классов, потому что может быть довольно много производных классов. Есть ли какой-нибудь лучший способ сделать это, о котором я просто подумал?

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

Любая помощь очень ценится, ребята.

Ответы [ 9 ]

13 голосов
/ 05 января 2009

Не уверен, что вы имеете в виду. Похоже, вы храните объекты по значению, и у вас есть массив Base. Это не сработает, потому что как только вы назначите Derived, этот объект будет преобразован в Base, и часть Derived объекта будет вырезана. Но я думаю, что вы хотите иметь массив указателей на базу:

Base * bases[NUM_ITEMS];
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases[i] = new Derived1;
    else if(r == 1)
        bases[i] = new Derived2;
    // ...
}

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

shared_ptr<Base> bases[NUM_ITEMS];
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases[i].reset(new Derived1);
    else if(r == 1)
        bases[i].reset(new Derived2);
    // ...
}

Теперь вы можете передать bases[x] другому shared_ptr, и он заметит, что вы получили более одной ссылки - он автоматически вызовет удаление, если последняя ссылка на объекты выйдет из области видимости. В идеале вы также должны заменить необработанный массив на std :: vector:

std::vector< shared_ptr<Base> > bases;
for(int i=0; i<NUM_ITEMS; i++) {
    int r = get_random_integer();
    if(r == 0)
        bases.push_back(shared_ptr<Base>(new Derived1));
    else if(r == 1)
        bases.push_back(shared_ptr<Base>(new Derived2));
    // ...
}

Тогда вы можете передавать вектор, не теряя его размера, и вы можете динамически добавлять к нему элементы по требованию. Получите размер вектора, используя bases.size(). Подробнее о shared_ptr здесь .

Преобразование из базового класса в производный класс должно выполняться только в случае крайней необходимости . Обычно вы хотите использовать метод под названием polymorphism, что означает, что вы вызываете функцию по базовому указателю, но на самом деле она вызывает функцию, определенную в производном классе, с одинаковой сигнатурой (имя и параметры одного типа). и сказано override это. Прочитайте статью в Википедии об этом. Если вам действительно нужно выполнить приведение, вы можете сделать это так для необработанного указателя:

Derived1 * d = &dynamic_cast<Derived1&>(*bases[x]);

Использование dynamic_cast гарантирует, что при приведении к неверному типу (т. Е. Типу, который вы приводите, не является тип, созданный и назначенный базовому указателю), вы получите исключение, выданное оператором. Для случая shared_ptr тоже есть способы:

shared_ptr<Derived1> d = dynamic_pointer_cast<Derived1>(bases[x]);
if(d) {
    // conversion successful, it pointed to a derived. d and bases[x] point still 
    // to the same object, thus share it. 
}
10 голосов
/ 05 января 2009

Преобразование из базового класса в производный класс в большинстве случаев является неприятным запахом кода.

Полиморфизм , с использованием виртуальных методов, является лучшим подходом.

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

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

1 голос
/ 05 января 2009

Я до сих пор не знаю, что вы пытаетесь сделать. Вы не можете опускать объекты в целом, а просто указатели на объекты. Обычный способ сделать это что-то вроде dynamic_cast (foo), где foo имеет тип bankAccount *. Это даст вам указатель на объект MoneyMarket или нулевой указатель.

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

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

1 голос
/ 05 января 2009

Я думаю, что нужна дополнительная информация. Почему ты удручен? вы делаете то же самое для каждого производного класса? Если это так, вы должны использовать интерфейс или поместить код в базовый класс (или в оба).

Например, если у вас есть массив фигур (основание), и вы хотите рассчитать их площадь, вы можете сделать следующее:

    interface IShape
    {
       double GetArea();
    }

    class Square : IShape
    {
       double GetArea()
       {
          return sideLengh*sideLength;
       }
       ...
    }

    class FunnyShape : IShape
    {
       //do funny stuff
       ...
    } 

    ...

void calcTotalArea(List<IShape> shapes)
{
   double total = 0.0;
   foreach (IShape s in shapes)
   {
      total += s.GetArea();
   }
   return total;
}
1 голос
/ 05 января 2009

Используйте массив BaseClass*, а не массив BaseClass. (Или выберите библиотеку интеллектуальных указателей для управления памятью.)

0 голосов
/ 05 января 2009

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

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

0 голосов
/ 05 января 2009

Проблема здесь в том, что вы храните реальные объекты BankAccount в своем массиве, а не объекты SavingsAccount или MoneyMarket. Вы обнаружите, что приведение вверх в этих случаях не будет работать. Очевидно, что вы можете попытаться использовать reinterpret_cast, но есть вероятность, что он сломается. (представьте, что у SavingAccount есть дополнительная переменная-член - процентная ставка - это сделает его на несколько байт больше, чем базовый класс)

Что вам нужно сделать, это хранить указатели на объекты BankAccount, тогда они могут быть любым производным классом, который вам нравится - вы выделяете SavingAccount и помещаете указатель на него в своем массиве. Это прекрасно работает, компилятор успешно отслеживает тип объекта, и вы выделили всю память, необходимую для одного из этих производных типов.

Теперь, когда ваши сберегательные или денежные счета построены правильно и хранятся где-то со ссылками на них в вашем массиве, вы можете использовать dynamic_cast для преобразования указателя, содержащегося в массиве, в реальный тип - если вы попытаетесь преобразовать один из них. из этих указателей на объект неправильного типа вы получите исключение. (очевидно, вы не хотите хранить объект SavingAccount, а затем обращаться к нему, как если бы это был объект moneyMarket!), вы получите действительный указатель на объект, только если тип правильный. Вам нужно будет включить RTTI, чтобы это работало (т. Е. Компилятор будет отслеживать тип каждого объекта).

Альтернативный способ достичь того, чего вы хотите, кроме использования старого доброго объединения в стиле C, - это поместить все методы, к которым вы получите доступ, в базовый класс и вызывать их непосредственно в базовых указателях, которые вы храните в своем массив.

и после того, как все это напечатано - Литб говорит почти то же самое, только намного лучше, чем я, - поставьте ему галочку!

0 голосов
/ 05 января 2009

Если у вас есть массив объектов bankAccount [40], вы никогда не используете свои производные классы.

Приведение к производным классам очень Java-иш, потому что там у вас есть оператор instanceof, и все это очень естественно. В C ++ вы можете эмулировать его с помощью reinterpret_cast (), но это считается плохим стилем.

Что вы пытаетесь сделать в первую очередь? Бросить все в один контейнер?

0 голосов
/ 05 января 2009

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

вот мой пример:

class bankAccount
{
   int money;
   bankAccount() {};

   int MoneyInAccount() {return money;};
}

class savingsAccount : public bankAccount
{
   int SavingsInterest();
}

class MoneyMarket : public bankAccount
{
    int CalculateAllPastReturns();
}

Тогда в моей программе у меня был массив bankAccount [40] (это был не указатель). Я надеялся, что смогу разыграть BankAccount в SavingsAccount или MoneyMarket или CheckingAccount и т. Д. У каждого из них есть свои уникальные классы. Оттуда программа может иметь коллекцию различных банковских счетов и их уникальную информацию.

Конечно, я знаю, что пытаться так разыгрывать плохо, но я не был уверен, что делать.

Я надеялся, что просто упустил что-то с указателями и т. Д. В любом случае, я надеюсь, что это было немного более конкретным. Большое спасибо за помощь!

...