STL List - тип данных как объект указателя - PullRequest
0 голосов
/ 19 октября 2011

У меня проблема с использованием наследования и библиотеки списков 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 ]

1 голос
/ 19 октября 2011

Вы предполагаете, что элемент, который вы извлекаете из списка, является экземпляром 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;
}
1 голос
/ 19 октября 2011

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

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

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

EDIT:

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

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&, не зная, что это за тип - полиморфизм - ваш друг.)

0 голосов
/ 19 октября 2011

С помощью функции сравнения, определенной для указателей на 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
}
0 голосов
/ 19 октября 2011

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

0 голосов
/ 19 октября 2011

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

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 (или других упорядоченных контейнеров).

...