STL List - тип данных в качестве объекта указателя

У меня проблема с использованием наследования и библиотеки списков STL...

Скажем, у меня есть абстрактный базовый класс с двумя производными классами (где определены все операторы сравнения). Список объявлен как

list<StoreItem*> items;

Я вставляю производный класс (абстрактного базового класса, StoreItem) под названием Food или Clothing. Я делаю новый указатель StoreItem, который вот-вот будет вставлен:

StoreItem* item = new Food(arguments here);

Теперь я хочу вставить этот новый элемент (по порядку) в список, и моя попытка заключается в следующем:

list<StoreItem*>::iterator iter;
for (iter = inventory.begin(); iter != inventory.end(); iter++)
{
    if (*item < **iter)
        break; // break out to insert
}

inventory.insert(iter, item);

Что-то я делаю не так? Кроме того, как бы я вытащил информацию из инвентаря? (например: Food tempFruit(**iter) с использованием конструктора копирования).

Заранее спасибо! Хорошего дня.

5 ответов

Решение

Вы предполагаете, что предмет, который вы извлекаете из списка, является Food пример; однако компилятор этого не знает. Когда вы создаете новый экземпляр Food из элемента в списке (элемент с видимым типом StoreItem), вы пытаетесь позвонить Food::Food(const StoreItem) или что-то совместимое. Зачем? Поскольку итератор указывает на StoreItem* это может быть примером StoreItem объект или экземпляр любого класса, производного от StoreItem, такие как Food,

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

Food *food = dynamic_cast<Food*>(*iter);
if (food != NULL) {
   // perform food related logic
   std::cout << "Ingredients: " << food->ingredients() << std::endl;
}
else {
   std::cout << "I can't eat that!" << std::endl;
}

Это будет работать, если вы определили StoreItem::operator<, но есть и другой способ, который может быть немного лучше. STL имеет холодную сортировку. Вы могли бы определить < за StoreItem*затем используйте list<...>::sort(),

(И вы, наверное, уже думали об определении своего SortedItemList класс, который обрабатывает внутреннюю сортировку.)

И да, tempMovie(**iter) будет работать, среди других способов.

РЕДАКТИРОВАТЬ:

Я думаю, что слишком рано говорил о том, чтобы вытащить что-то из инвентаря. Это работает:

list<StoreItem *>::iterator citr = items.begin();

Food *fp = dynamic_cast<Food *>(*citr);

Food ff(*fp);

Обратите внимание, что вы должны знать, что это StoreItem* на самом деле указывает на Food- если это указывает на Clothing Вы получите ошибку сегментации или хуже. Чтобы узнать, вы могли бы реализовать свой собственный StoreItem::whatTypeAmI()или используйте идентификацию типа C++ во время выполнения:

#include <typeinfo>
...
Food a;
StoreItem *sp = *citr;
if(typeid(*sp)==typeid(a))
{
  // it's a Food
}

(Помните, что вы можете многое сделать с StoreItem* или же StoreItem& не зная, что это типа - полиморфизм твой друг.)

Вместо доморощенного решения вы можете прибегнуть к boost::ptr_list, Это значительно облегчает жизнь, если вы собираетесь хранить указатели в STL-подобных контейнерах. Тогда все, что вам нужно, это определить operator< за любой предмет, который вы пытаетесь вставить. Помни что ptr_list не предназначен для совместного использования. Для достижения этого использования std::shared_ptrS в std::list и специализироваться std::less для тебя shared_ptr тип.

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

template<>
struct std::less<base*>
{
   bool operator()(const base* lhs, const base* rhs) const
   {
      return *lhs < *rhs;
   }
};

typedef set<base*> S;

int _tmain(int argc, _TCHAR* argv[])
{
    base able(std::string("able"));
    base baker(std::string("baker"));
    base charlie(std::string("charlie"));

    S inventory;
    inventory.insert(&charlie);
    inventory.insert(&able);
    inventory.insert(&baker);

    for (S::iterator i = inventory.begin(); i != inventory.end(); ++i)
        std::cout << **i << endl;
    return 0;
}

выход:
в состоянии
пекарь
Чарли

Можно найти время, прежде чем открыть эту идиому. Что происходит, так это то, что вы специализируете библиотечный шаблон std::less для T=base*; затем он словно по волшебству вставляется в аргумент компаратора по умолчанию для std::set (или других упорядоченных контейнеров).

С функцией сравнения, определенной для указателей на StoreItem Вы можете сократить свой код вставки следующим образом:

bool less_ptr( const StoreItem*& lhs, const StoreItem*& rhs )
{
    return *lhs < *rhs;
}

Вставка:

StoreItem* item = new Food(arguments here);
inventory.insert( std::upper_bound( inventory.begin(), inventory.end(), item, less_ptr ), item);

std::upper_bound (#include <algorithm>) предполагает, что ваш список отсортирован, так что это применимо, если вы сохраняете список отсортированным всегда.

Что касается извлечения данных, есть две вещи, которые следует учитывать:

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

Вы можете сделать это:

Food* foodObj = NULL;
Clothing* clothesObj = NULL;

list<StoreItem *>::iterator it = inventory.find( /* something */ );
StoreItem* item = *it;

item->DoSomethingWithAnyStoreItem(); // It's best to only use such methods

// But if you need something only a derived class has...
foodObj = dynamic_cast<Food*>(item);
clothesObj = dynamic_cast<Clothes*>(item);

if( foodObj != NULL )
{
    foodObj->DoSomethingWithFood();
    Food newFood( *foodObj );
    newFood.DoSomethingWithCopyOfFood();
}
else if( clothesObj != NULL )
{
    clothesObj->DoSomethingWithClothes();
}
else
{
    // It's neither Food, nor Clothes
}
Другие вопросы по тегам