Вариант один вы предложили. Всё пишу на C++03, без шаблонов. C++11 и шаблоны, разумеется, дадут больше вариантов.
class Object {
public:
virtual ~Object() {}
};
class List { // interface
protected:
virtual Object& getAt(size_t i) = 0;
public:
virtual size_t size() const = 0;
inline Object& at(size_t i) { return getAt(i); }
inline const Object& at(size_t i) const
{ return const_cast<List*>(this)->getAt(i); }
virtual ~List() {}
};
Вариант 2. С ним пользователю не так просто, но если к нему ещё добавить лямбда-функции C++11 — вообще бомба будет!
class ListCallback { // interface
public:
virtual void act(size_t index, Object& object) = 0;
virtual ~ListCallback() {}
};
class ListConstCallback { // interface
public:
virtual void act(size_t index, const Object& object) = 0;
virtual ~ListConstCallback() {}
};
class List2 { // interface
public:
virtual size_t size() const = 0;
virtual void enumerate(ListCallback& body) = 0;
virtual void enumerate(ListConstCallback& body) const = 0;
};
Например, для динамического массива, у которого быстрый доступ по номеру, будет такое тело
void DynArray::enumerate(ListConstCallback& body) const {
size_t sz = size();
for (size_t i = 0; i < sz; ++i)
body(i, operator[](i));
}
Разумеется, если вы нагрузкой сделаете не абстрактный Object, а что-то окончательное, dynamic_cast не нужен будет.
Вариант 3 принят в Java. Один недостаток — мого кода писать для const-корректности, так что с вашего позволения опущу.
class VirtualListIterator { // interface
public:
virtual bool next() = 0;
virtual Object& value() = 0;
virtual ~VirtualListIterator() {}
};
class ListIterator { // Назван так для красоты, по сути это умный указатель
private:
VirtualListIterator* ptr;
// Запретим копирование и op=, но при желании можно реализовать и их
ListIterator(ListIterator&) {}
void operator=(const ListIterator&) {}
public:
ListIterator() : ptr(NULL) {}
ListIterator(VirtualListIterator* value) : ptr(value) {}
~ListIterator() { delete ptr; }
bool next() { return ptr->next(); }
Object* operator->() const { return &ptr->value(); }
Object& operator*() const { return ptr->value(); }
};
class List3 { // interface
public:
virtual size_t size() const = 0;
virtual VirtualListIterator* enumerate() = 0;
};
class Number : public Object {
public:
int value;
};
// И пользоваться этим так...
void doSmth(List3& aList) {
ListIterator it(aList.enumerate());
while (it.next()) {
std::cout << dynamic_cast<Number&>(*it).value << std::endl;
}
}
Во всех трёх вариантах не забывайте: если нагрузка — не что-то окончательное, а абстрактный класс, элементы придётся хранить по указателю и вручную уничтожать.
Да, и из вас будет хороший инженер, если вы сразу же задумываетесь о производительности.