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

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

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

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

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

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


person DuncanACoulter    schedule 27.08.2010    source источник


Ответы (6)


По оператору [] можно узнать только доступ.
Даже если внешний объект использует бесплатную версию, это не означает, что запись будет иметь место, а может быть.

Таким образом, вам нужно вернуть объект, который может обнаруживать изменения.
Лучший способ сделать это — обернуть объект классом, который переопределяет operator=. Затем эта оболочка может сообщить хранилищу, когда объект был обновлен. Вы также хотели бы переопределить operator Type (приведение), чтобы константную версию объекта можно было получить для доступа на чтение.

Тогда мы могли бы сделать что-то вроде этого:

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);
}

Более простая альтернатива:
Вместо того, чтобы предоставлять оператор[], вы предоставляете конкретный метод set для объекта хранилища и предоставляете доступ для чтения только через оператор[]

person Martin York    schedule 27.08.2010
comment
Спасибо вам и Тони за отличные ответы. Мне нужно будет более подробно изучить ваше предложение, но мне нравится идея. Что касается вашей более простой альтернативы... Я согласен, однако моя цель состояла в том, чтобы предоставить полнофункциональный тип данных, поэтому принуждение программиста-клиента смешивать перегрузку оператора и функции-члены мутатора мне не очень понравились. - person DuncanACoulter; 27.08.2010

Вы можете заставить (неконстантный) operator[] возвращать прокси-объект, который хранит ссылку или указатель на контейнер, и в котором operator= сигнализирует о контейнере обновления.

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

person Tony Delroy    schedule 27.08.2010
comment
Спасибо, у меня были сомнения по поводу использования константной версии оператора. - person DuncanACoulter; 27.08.2010

Еще одно элегантное (ИМХО) решение... На самом деле оно основано на том, что перегрузка 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) и ни на что не влияет в рантайме — ни на память, ни на производительность.

И в сухом остатке - это гораздо элегантнее (ИМХО), чем создание всяких прокси присваивания и т.п. Должен констатировать, что утверждение "От оператора[] можно только реально сказать доступ< /сильный>" неверно. В соответствии со стандартом C++ возврат динамически размещенного объекта или глобальной переменной по ссылке является окончательным способом разрешить его прямую модификацию, включая случай перегрузки [].

Был протестирован следующий код:

#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

person dzilbers    schedule 24.02.2014
comment
Обязан ли пользователь теперь использовать эту теневую ссылку при чтении объекта? Я бы предпочел, чтобы объект обрабатывал различие между чтением и записью, не обременяя пользователя. Если пользователю необходимо изменить свое поведение для чтения по сравнению с записью, он также может использовать отдельные функции доступа и мутатора. - person DuncanACoulter; 25.02.2014
comment
Послушайте, целью здесь было использование перегрузки индексатора, позволяющей отдельно реализовать аксессор или мутатор. Нет другого пути, кроме создания прокси-класса для мутатора (т.е. = перегрузки), как указано в ответах до меня. Однако я считаю это решение немного уродливым, и в любом случае оно требует использования различных конструкций для изменения/доступа. Другой способ, который я предлагаю, - использование дифференцирования 'const' для [] перегрузки. Это различие имеет свои требования к C++, как я объяснял выше. - person dzilbers; 27.02.2014
comment
Из тех, кто выступает за подход к дифференциации, основанный на константах, ваш был самым подробным, поэтому +1 за это. Однако лично я предпочитаю прокси-подход. - person DuncanACoulter; 27.02.2014
comment
Согласен - это дело вкуса и предпочтений. Я вообще считаю программирование искусством :) - person dzilbers; 28.02.2014

Возвратите прокси-объект, который будет иметь:

  • operator=(Type const &) перегружен для записи
  • оператор Type() для чтения
person Tomek    schedule 27.08.2010

в приведенном вами примере доступа вы можете получить различие, используя версию const:

const Type& operator [] ( int index ) const;

на боковой заметке использование size_t в качестве индекса избавляет от необходимости проверять, имеет ли индекс >= 0

person stijn    schedule 27.08.2010

person    schedule
comment
Это не позволяет классу быть уведомленным о том, происходит ли чтение или запись. Под этим я подразумеваю, что внутри функции-члена operator[] вы не знаете, осуществляется ли доступ или изменение значения. - person DuncanACoulter; 15.12.2016