Подтвердить что ты не робот

Перегрузка оператора индексирования индексов индексирования С++ [] таким образом, чтобы можно было отвечать на обновления

Рассмотрим задачу записи индексируемого класса, который автоматически синхронизирует его состояние с каким-либо внешним хранилищем данных (например, с файлом). Для этого класс должен быть проинформирован об изменениях индексированного значения, которое может произойти. К сожалению, обычный подход к перегрузке оператора [] не позволяет этого, например...

Type& operator[](int index)
{
    assert(index >=0 && index < size);
    return state[index];
}

Есть ли способ различать доступное значение и изменяемое значение?

Type a = myIndexable[2]; //Access
myIndexable[3] = a;  //Modification

Оба этих случая возникают после возвращения функции. Есть ли другой подход к перегрузке оператора [], который, возможно, имеет больше смысла?

4b9b3361

Ответ 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;
    }