web-dev-qa-db-fra.com

Surcharge de l'opérateur d'indice d'indexation C++ [] de manière à permettre des réponses aux mises à jour

Considérez la tâche d'écrire une classe indexable qui synchronise automatiquement son état avec un magasin de données externe (par exemple, un fichier). Pour ce faire, il faudrait informer la classe des modifications qui pourraient survenir dans la valeur indexée. Malheureusement, l'approche habituelle de surcharge de l'opérateur [] ne le permet pas, par exemple ...

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

Y at-il un moyen de faire la distinction entre une valeur en cours d’accès et une valeur en cours de modification?

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

Ces deux cas se produisent après le retour de la fonction. Existe-t-il une autre approche de surcharge de l'opérateur [] qui aurait peut-être plus de sens? 

25
DuncanACoulter

De l'opérateur [], vous pouvez seulement vraiment dire l'accès.
Même si l'entité externe utilise la version sans coût, cela ne signifie pas qu'une écriture aura lieu, mais qu'elle pourrait avoir lieu. 

En tant que tel, vous devez renvoyer un objet capable de détecter une modification.
La meilleure façon de procéder consiste à envelopper l'objet d'une classe qui remplace le operator=. Ce wrapper peut alors informer le magasin lorsque l'objet a été mis à jour. Vous voudriez également remplacer le operator Type (transt) de sorte qu'une version const de l'objet puisse être extraite pour les accès en lecture.

Ensuite, nous pourrions faire quelque chose comme ceci:

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

Une alternative plus simple est:
Plutôt que de fournir à l'opérateur [], vous fournissez une méthode de définition spécifique sur l'objet magasin et ne fournissez qu'un accès en lecture par l'intermédiaire de l'opérateur []

15
Martin York

Vous pouvez demander à l'opérateur (non-const) [] de renvoyer un objet proxy qui garde une référence ou un pointeur sur le conteneur et dans lequel operator = signale le conteneur de la mise à jour.

(L’idée d’utiliser const vs non-const operator [] est un fouillis rouge ... vous savez peut-être que vous venez de donner un accès non-const à l’objet, mais vous ne savez pas si cet accès est toujours en cours utilisé pour une lecture ou une écriture, lorsque cette écriture est terminée, ou qu’il existe un mécanisme permettant de mettre à jour le conteneur par la suite.)

10
Tony Delroy

Une autre solution élégante (IMHO) ... En fait, elle est basée sur le fait que la surcharge const n'est appelée que lorsqu'elle est utilisée sur un objet const. Commençons par créer deux surcharges - telles quelles requis, mais en utilisant des emplacements différents:

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

Vous devez maintenant créer une référence d'ombre de votre objet lorsque vous devez la "lire" comme suit:

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

La création de cette déclaration shadow ne crée en réalité rien dans la mémoire. Il crée simplement un autre nom pour votre objet avec un accès "const". Tout est résolu au stade de la compilation (y compris l’utilisation de la surcharge const) et n’affecte en rien l’exécution, ni la mémoire ni les performances.

Et l’essentiel - c’est beaucoup plus élégant (à mon humble avis) que de créer des mandataires d’affectation, etc. Je dois préciser que la déclaration "de l’opérateur [], vous ne pouvez vraiment dire que access" Selon la norme C++, renvoyer un objet alloué dynamiquement ou une variable globale par référence est le moyen ultime d'autoriser sa modification directe, y compris le cas de surcharge [].

Le code suivant a été testé:

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

Et les résultats sont:

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

5
dzilbers

Retourne un objet proxy qui aura:

  • operator = (Type const &) surchargé pour les écritures
  • type d'opérateur () pour les lectures
4
Tomek

dans l'exemple d'accès que vous donnez, vous pouvez obtenir une distinction en utilisant une version de const:

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

sur une note de bas de page, utiliser size_t comme index supprime la nécessité de vérifier si index> = 0

1
stijn
    #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;
    }
0
Praveer Kumar