以允许响应更新的方式重载C++索引下标operator []

Dun*_*ter 25 c++ indexing operator-overloading

考虑编写可索引类的任务,该类自动将其状态与某些外部数据存储(例如文件)同步.为了做到这一点,需要让类知道可能发生的索引值的更改.不幸的是,重载operator []的常用方法不允许这样做,例如......

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

我有什么方法可以区分被访问的值和被修改的值?

Type a = myIndexable[2]; //Access
myIndexable[3] = a;  //Modification
Run Code Online (Sandbox Code Playgroud)

这两种情况都在函数返回后发生.有没有其他方法来重载operator [],这可能更有意义?

Mar*_*ork 15

From the operator[] you can only really tell access.
Even if the external entity uses the non cost version this does not mean that a write will take place rather that it could take place.

因此,您需要做的是返回一个可以检测修改的对象.
执行此操作的最佳方法是使用覆盖该对象的类来包装对象operator=.然后,此包装器可以在更新对象时通知存储.您还希望覆盖operator Type(强制转换),以便可以检索对象的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);
}
Run Code Online (Sandbox Code Playgroud)

一个更简单的替代方案是:
不是提供operator [],而是在store对象上提供特定的set方法,并且只通过operator []提供读访问权限.


Ton*_*roy 10

您可以让(非const)运算符[]返回一个代理对象,该对象保持对容器的引用或指针,以及哪个operator =表示更新的容器.

(The idea of using const vs non-const operator[] is a red herring... you may know that you've just given away non-const access to the object, but you don't know if that access is still being used for a read or a write, when that write completes, or have any mechanism for updating the container thereafter.)


dzi*_*ers 5

另一个优雅的(恕我直言)解决方案......实际上它是基于这样一个事实,即仅在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];
}
Run Code Online (Sandbox Code Playgroud)

现在,您需要在需要"读取"对象时创建对象的阴影引用,如下所示:

const Indexable& myIndexableRead = myIndexable; // create the shadow
Type a = myIndexableRead[2]; //Access
myIndexable[3] = a;  //Modification
Run Code Online (Sandbox Code Playgroud)

创建此影子声明实际上并不在内存中创建任何内容.它只是通过"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;
}
Run Code Online (Sandbox Code Playgroud)

结果是:

20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 50