Перегрузка оператора индексирования индексов индексирования С++ [] таким образом, чтобы можно было отвечать на обновления
Рассмотрим задачу записи индексируемого класса, который автоматически синхронизирует его состояние с каким-либо внешним хранилищем данных (например, с файлом). Для этого класс должен быть проинформирован об изменениях индексированного значения, которое может произойти. К сожалению, обычный подход к перегрузке оператора [] не позволяет этого, например...
Type& operator[](int index)
{
assert(index >=0 && index < size);
return state[index];
}
Есть ли способ различать доступное значение и изменяемое значение?
Type a = myIndexable[2]; //Access
myIndexable[3] = a; //Modification
Оба этих случая возникают после возвращения функции. Есть ли другой подход к перегрузке оператора [], который, возможно, имеет больше смысла?
Ответы
Ответ 1
От оператора [] вы можете только сказать доступ.
Даже если внешняя сущность использует недорогую версию, это не означает, что запись будет происходить, а может произойти.
Как таковая Что вам нужно сделать, это вернуть объект, который может обнаружить модификацию.
Лучший способ сделать это - обернуть объект классом, который переопределяет operator=
. Затем этот обертку может информировать магазин о том, когда объект был обновлен. Вы также хотели бы переопределить operator Type
(cast), чтобы можно было восстановить версию const для доступа к чтению.
Тогда мы могли бы сделать что-то вроде этого:
class WriteCheck;
class Store
{
public:
Type const& operator[](int index) const
{
return state[index];
}
WriteCheck operator[](int index);
void stateUpdate(int index)
{
// Called when a particular index has been updated.
}
// Stuff
};
class WriteCheck
{
Store& store;
Type& object;
int index;
public: WriteCheck(Store& s, Type& o, int i): store(s), object(o), index(i) {}
// When assignment is done assign
// Then inform the store.
WriteCheck& operator=(Type const& rhs)
{
object = rhs;
store.stateUpdate(index);
}
// Still allow the base object to be read
// From within this wrapper.
operator Type const&()
{
return object;
}
};
WriteCheck Store::operator[](int index)
{
return WriteCheck(*this, state[index], index);
}
Более простая альтернатива:
Вместо предоставления оператора [] вы предоставляете определенный метод набора объектов хранилища и предоставляете доступ только для чтения через оператора []
Ответ 2
Вы можете иметь (неконстантный) оператор [] вернуть прокси-объект, который хранит ссылку или указатель на контейнер, и в котором оператор = сигнализирует контейнер обновления.
(Идея использования const vs non-const operator [] - это красная селедка... вы можете знать, что вы только что отдали неконстантный доступ к объекту, но вы не знаете, доступен ли этот доступ все еще используется для чтения или записи, когда эта запись завершается или у нее есть какой-либо механизм для обновления контейнера после этого.)
Ответ 3
Еще одно элегантное решение (IMHO)...
Фактически он основан на том факте, что перегрузка const вызывается только при использовании в const-объекте.
Позволяет сначала создать две [] перегрузки - как требуется, но используя разные местоположения:
Type& operator[](int index)
{
assert(index >=0 && index < size);
return stateWrite[index];
}
const Type& operator[](int index) const
{
assert(index >=0 && index < size);
return stateRead[index];
}
Теперь вам нужно создать теневую ссылку вашего объекта, когда вам нужно "прочитать" его следующим образом:
const Indexable& myIndexableRead = myIndexable; // create the shadow
Type a = myIndexableRead[2]; //Access
myIndexable[3] = a; //Modification
Создание этого теневого объявления фактически ничего не создает в памяти. Он просто создает другое имя для вашего объекта с доступом "const". Все это разрешено на этапе компиляции (включая использование перегрузки const) и не влияет ни на что во время выполнения - ни на память, ни на производительность.
И нижняя строка - это намного более изящная (IMHO), чем создание прокси-адресов заданий и т.д. Я должен указать, что выражение " От оператора [] вы можете реально сказать доступ это неверно. Согласно стандарту С++, возвращение динамически выделяемого объекта или глобальной переменной по ссылке является окончательным способом разрешить его непосредственную модификацию, включая [] случай перегрузки.
Проверен следующий код:
#include <iostream>
using namespace std;
class SafeIntArray {
int* numbers;
int size;
static const int externalValue = 50;
public:
SafeIntArray( unsigned int size = 20 ) {
this->size = size;
numbers = new int[size];
}
~SafeIntArray() {
delete[] numbers;
}
const int& operator[]( const unsigned int i ) const {
if ( i < size )
return numbers[i];
else
return externalValue;
}
int& operator[]( const unsigned int i ) {
if ( i < size )
return numbers[i];
else
return *numbers;
}
unsigned int getSize() { return size; }
};
int main() {
SafeIntArray arr;
const SafeIntArray& arr_0 = arr;
int size = arr.getSize();
for ( int i = 0; i <= size ; i++ )
arr[i] = i;
for ( int i = 0; i <= size ; i++ ) {
cout << arr_0[i] << ' ';
}
cout << endl;
return 0;
}
И результаты:
20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 50
Ответ 4
Возвращает прокси-объект, который будет иметь:
- operator = (Тип const &), перегруженный для записи
- оператор Type() для чтения
Ответ 5
в примере доступа, который вы даете, вы можете получить различие с помощью версии const:
const Type& operator [] ( int index ) const;
в sidenote, используя size_t в качестве индекса, избавляется от необходимости проверки, если index >= 0
Ответ 6
#include "stdafx.h"
#include <iostream>
template<typename T>
class MyVector
{
T* _Elem; // a pointer to the elements
int _Size; // the size
public:
// constructor
MyVector(int _size):_Size(_size), _Elem(new T[_size])
{
// Initialize the elemets
for( int i=0; i< _size; ++i )
_Elem[i] = 0.0;
}
// destructor to cleanup the mess
~MyVector(){ delete []_Elem; }
public:
// the size of MyVector
int Size() const
{
return _Size;
}
// overload subscript operator
T& operator[]( int i )
{
return _Elem[i];
}
};
int _tmain(int argc, _TCHAR* argv[])
{
MyVector<int> vec(10);
vec[0] =10;
vec[1] =20;
vec[2] =30;
vec[3] =40;
vec[4] =50;
std::cout<<"Print vector Element "<<std::endl;
for (int i = 0; i < vec.Size(); i++)
{
std::cout<<"Vec["<<i<<"] = "<<vec[i]<<std::endl;
}
return 0;
}