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>
) предполагает, что ваш список отсортирован, так что это применимо, если вы сохраняете список отсортированным всегда.
Что касается извлечения данных, есть две вещи, которые следует учитывать:
- Если вы воссоздаете объекты с помощью конструктора копирования, вы создаете новые объекты, и их изменение не изменит объекты в списке, поэтому лучше использовать указатели
- Вы должны разделить путь кода в зависимости от типа сохраняемого объекта.
Вы можете сделать это:
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
}