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

发布于 2024-09-16 08:49:26 字数 426 浏览 3 评论 0原文

考虑编写一个可索引类的任务,该类自动将其状态与某些外部数据存储(例如文件)同步。为了做到这一点,类需要了解可能发生的索引值的变化。不幸的是,重载运算符[]的通常方法不允许这样做,例如...

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

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

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

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

Consider the task of writing an indexable class which automatically synchronizes its state with some external data-store (e.g. a file). In order to do this the class would need to be made aware of changes to the indexed value which might occur. Unfortunately the usual approach to overloading operator[] does not allow for this, for example...

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

I there any way to distinguish between a value being accessed and a value being modified?

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

Both of these cases occur after the function has returned. Is there some other approach to overloading operator[] which would perhaps make more sense?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

囚你心 2024-09-23 08:49:26

从操作符[]中你只能真正知道访问。
即使外部实体使用非成本版本,这并不意味着将发生写入而不是可以发生。

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

一个更简单的替代方案是:
您不是提供操作符[],而是在存储对象上提供特定的设置方法,并且仅通过操作符[]提供读取访问权限

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.

As such What you need to do is return an object that can detect modification.
The best way to do this is to wrap the object with a class that overrides the operator=. This wrapper can then inform the store when the object has been updated. You would also want to override the operator Type (cast) so that a const version of the object can be retrieved for read accesses.

Then we could do something like this:

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

An simpler alternative is:
Rather than provide the operator[] you provide a specific set method on the store object and only provide read access through the operator[]

牛↙奶布丁 2024-09-23 08:49:26

您可以让(非常量)operator[] 返回一个代理对象,该对象保留对容器的引用或指针,并且其中的operator= 表示更新的容器。

(使用 const 与非常量运算符 [] 的想法是一个转移注意力的想法......您可能知道您刚刚放弃了对该对象的非常量访问,但您不知道该访问是否仍在进行中用于读取或写入(当写入完成时),或者具有用于此后更新容器的任何机制。)

You can have (the non-const) operator[] return a proxy object that keeps a reference or pointer to the container, and in which operator= signals the container of the update.

(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.)

热鲨 2024-09-23 08:49:26

另一个优雅的(恕我直言)解决方案......
实际上,它基于这样一个事实: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 重载),并且不会影响运行时的任何内容 - 既不影响内存也不影响性能。

底线 - 它比创建任何赋值代理等要优雅得多(恕我直言)。我必须声明“从操作符[]中你只能真正告诉访问< /strong>”是不正确的。根据 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

Another elegant (IMHO) solution...
Actually it is based on the fact that the const overload is called only when used on const object.
Lets first create two [] overloads - as it is required, but using different locations:

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

Now you should create a shadow reference of your object when you need to "read" it as follows:

const Indexable& myIndexableRead = myIndexable; // create the shadow
Type a = myIndexableRead[2]; //Access
myIndexable[3] = a;  //Modification

Creating this shadow declaration does not actually create anything in the memory. It just creates another name for your object with "const" access. It is all resolved at the compilation stage (including usage of const overload) and does not affect anything in runtime - neither memory nor performance.

And the bottom line - it is much more elegant (IMHO) than creating any assignment proxies, etc. I must state that the statement "From the operator[] you can only really tell access" is incorrect. According to the C++ Standard, returning dynamically allocatted object or global variable by reference is ultimate way to allow its direct modification, including [] overload case.

Following code has been tested:

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

And the results are:

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

三月梨花 2024-09-23 08:49:26

返回一个代理对象,该对象将具有:

  • operator=(Type const &) 重载用于写入
  • 操作符 Type() 用于读取

Return a proxy object which will have:

  • operator=(Type const &) overloaded for writes
  • operator Type() for reads
红尘作伴 2024-09-23 08:49:26

在您给出的访问示例中,您可以通过使用 const 版本来获得区别:

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

在旁注中,使用 size_t 作为索引无需检查索引是否 >= 0

in the access example you give you can get a distinction by using a const version:

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

on a sidenote, using size_t as index gets rid of the need for checking if index >= 0

夜未央樱花落 2024-09-23 08:49:26
    #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;
    }
    #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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文