Comment empêcher la modification des données du tableau?

9

Disons que j'ai une classe qui ressemble à ceci (ce n'est qu'un exemple):

class A {
    double *ptr;
public:
    A() : ptr( new double[100] ) {}
    A( const A &other ) {
        other.ptr[7] = 15;
    }
    void doNotChangeMyData() const {
        ptr[43] = 14;
    }
    void changeMyData() {
        ptr[43] = 14;
    }
    ~A() { delete[] ptr; }
};

Le constconstructeur de copie et la doNotChangeMyDatafonction le rendent ptrimpossible à modifier; cependant, cela me permet toujours de modifier le contenu du tableau pointé par ptr.

Existe-t-il un moyen d'empêcher le contenu du ptrtableau de d'être modifié dans les constinstances uniquement, à moins de "faire attention" (ou de s'éloigner du pointeur brut)?

Je sais que je pourrais faire quelque chose comme

void doNotChangeMyData() const {
    const double *const ptr = this->ptr;
    ptr[43] = 14; // then this would fail to compile
}

Mais je préfère ne pas avoir à ...

ChrisMM
la source
1
vous pouvez utiliser unstd::vector
idclev 463035818
std::vector::operator[]()peut modifier les valeurs à droite?
marvinIsSacul
@ formerlyknownas_463035818 Question modifiée donc pas une option;) C'est plus une question théorique, mais oui, ça vectormarcherait.
ChrisMM
2
@marvinIsSacul bien sûr, mais std::vector::operator[]() constrenvoie une constréférence
idclev 463035818
@ChrisMM ce que j'attendais, je voulais juste mentionner l'éléphant dans la salle :)
idclev 463035818

Réponses:

7

Les pointeurs ne se propagent pas const. L'ajout constau type double*donne double* const, ce qui entraîne une non- constvaleur lorsqu'il est déréférencé.

Au lieu de cela, vous pouvez utiliser std::vector:

class A {
    std::vector<double> data(100);
public:
    // no explicit copy ctor or dtor
};

a std::array:

class A {
    std::array<double, 100> data{};
public:
    // no explicit copy ctor or dtor
};

ou un tableau intégré (non recommandé):

class A {
    double data[100] {};
public:
    // no explicit copy ctor or dtor
};

Les trois options se propagent const.

Si vous voulez vraiment utiliser des pointeurs (fortement déconseillés), utilisez au moins un std::unique_ptrpour éviter la gestion manuelle de la mémoire. Vous pouvez utiliser l' std::experimental::propagate_constencapsuleur des principes fondamentaux de la bibliothèque 2 TS:

class A {
    std::experimental::propagate_const<std::unique_ptr<double[]>> ptr;
public:
    A()
        : ptr{new double[100] {}}
    {
    }
    // manual copy ctor
    A(const A& other)
        : ptr{new double[100]}
    {
        std::copy_n(other.ptr.get(), 100, ptr.get());
    }
    // defaulted move ctor & dtor
    // assignment operator, etc.
    // ...
};

Ce n'est pas encore dans la norme, mais de nombreux compilateurs le prennent en charge. Bien sûr, cette approche est inférieure aux conteneurs appropriés.

LF
la source
essayer de le faire sans changer le type de données sous-jacent, plus une question théorique qu'autre chose. Si ce n'est pas possible, je l'accepterai comme impossible.
ChrisMM
@ChrisMM J'ai mis à jour la réponse avec une solution de pointeur. Mais pourquoi :)
LF
"Pourquoi" est difficile à répondre, plus une curiosité. "Tableau intégré" ou std::arrayne fonctionne pas, si vous ne connaissez pas la taille au moment de la compilation. vectorajoute des frais généraux; unique_ptrn'ajoute pas de surcharge mais si le pointeur doit être partagé, alors vous avez besoin de celui shared_ptrqui ajoute la surcharge. Je ne pense pas que VS supporte actuellement propagate_const(au moins le fichier d'en-tête référencé par cppreference n'existe pas avec /std:c++latest) :(
ChrisMM
1
@ChrisMM La surcharge de vectorTBH est souvent surestimée, en particulier par rapport à l'effort de gestion manuelle de la mémoire. De plus, si vous partagez les pointeurs manuellement, vous devez utiliser un décompte de références, de sorte que la surcharge n'est pas particulière à shared_ptr. Je ne savais pas que VS ne supporte pas propagate_constencore (GCC et Clang le supportent tous les deux IIRC), mais il n'est pas difficile de déployer le nôtre selon les spécifications.
LF
Je conviens que la surcharge est minime, mais il y a des raisons d'utiliser des pointeurs bruts lorsque les performances sont critiques (mémoire et temps). J'utilise parfois a vectorpuis prend son contenu via .data()ou &vec[0]et travaille directement avec cela à la place. Dans le cas du partage, j'ai souvent un propriétaire du pointeur qui crée et supprime, mais d'autres classes partagent les données.
ChrisMM