Как вы сказали, проблема в том, что экземпляры Something
привязаны к объекту, который он содержит. Итак, давайте попробуем развязать их.
Ключевым моментом, который следует помнить, является то, что в ООП общедоступные неконстантные члены данных обычно не одобряются. В вашей текущей реализации каждый экземпляр Something
связан с наличием элемента данных T x
, который является общедоступным. Вместо этого лучше сделать абстракцию, т.е. вместо этого предоставить методы доступа:
class Something : IInterface
{
private:
T x;
public:
T GetX()
{
return x;
}
};
Теперь пользователь знает, что это за вещь x
, тем более, что существует x
.
Это хороший первый шаг, однако, поскольку вы хотите, чтобы x
ссылался на разные объекты в разное время, нам в значительной степени потребуется сделать x
указателем. И в качестве уступки обычному коду, мы также заставим GetX()
возвращать константную ссылку, а не обычное значение:
class Something: IInterface
{
private:
T *x;
public:
T const& GetX()
{
return *x;
}
};
Теперь реализовать методы в IInterface
тривиально:
class Something: IInterface
{
private:
T *x;
public:
T const& GetX()
{
return *x;
}
T& operator*()
{
return *x;
}
T* operator->()
{
return x;
}
Something& operator++()
{
++x;
return *this;
}
};
Оператор ++
теперь тривиален - он действительно просто применяет ++
к x
.
Пользователь теперь не знает, что указатель использовался. Все, что они знают, это то, что их код работает правильно. Это самый важный момент в принципе абстракции данных ООП.
Редактировать
Что касается реализации begin
и end
методов Container
, это также не должно быть слишком сложным, но потребует некоторых изменений Container
.
Прежде всего, давайте добавим приватный конструктор к Something
, который принимает указатель на начальный объект. Мы также сделаем MyContainer
другом Something
:
класс Something: IInterface
{
friend class MyContainer; // Can't test the code right now - may need to be MyContainer<T> or ::MyContainer<T> or something.
private:
T *x;
Something( T * first )
: x(first)
{
}
public:
T const& GetX()
{
return *x;
}
T& operator*()
{
return *x;
}
T* operator->()
{
return x;
}
Something& operator++()
{
++x;
return *this;
}
};
Делая конструктор приватным и устанавливая зависимость от друга, мы гарантируем, что только MyContainer может создавать новые Something
итераторов (это защищает нас от перебора по случайной памяти, если пользователь дал что-то ошибочное) .
Далее мы немного изменим MyContainer, чтобы вместо массива Something
у нас был просто массив T
:
class MyContainer
{
...
private:
T *data;
};
Прежде чем мы перейдем к реализации begin
и end
, давайте внесем это изменение в Container
, о котором я говорил:
template<typename T, typename IteratorType>
class Container {
public:
...
// These prototype are the key. Notice the return type is IteratorType (value, not reference)
virtual IteratorType begin() = 0;
virtual IteratorType end() = 0;
};
Таким образом, вместо того, чтобы полагаться на ковариацию (которая в данном случае будет действительно трудной), мы используем небольшую магию шаблонов, чтобы делать то, что мы хотим.
Конечно, поскольку Container теперь принимает другой параметр типа, нам нужно соответствующее изменение на MyContainer
; а именно нам нужно предоставить Something
в качестве параметра типа для Container
:
template<class T>
class MyContainer : Container<T, Something>
...
И методы begin
/ end
теперь просты:
template<class T>
MyContainer<T>::begin()
{
return Something(data);
}
template<class T>
MyContainer<T>::end()
{
// this part depends on your implementation of MyContainer.
// I'll just assume your have a length field in MyContainer.
return Something(data + length);
}
Итак, это то, что у меня есть для размышлений в полночь. Как я уже упоминал выше, в настоящее время я не могу протестировать этот код, поэтому вам, возможно, придется немного его настроить. Надеюсь, это делает то, что вы хотите.