Wie kann eine Änderung an Array-Daten verhindert werden?

9

Angenommen, ich habe eine Klasse, die so aussieht (dies ist nur ein Beispiel):

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

Die constsowohl im Kopierkonstruktor als auch in der doNotChangeMyDataFunktion machen es so, dass ptres nicht geändert werden kann; Auf diese Weise kann ich jedoch weiterhin den Inhalt des Arrays ändern, auf das von verwiesen wird ptr.

Gibt es eine Möglichkeit zu verhindern, dass der Inhalt des ptrArrays nur in constInstanzen geändert wird , ohne "vorsichtig" zu sein (oder sich vom Rohzeiger zu entfernen)?

Ich weiß, ich könnte so etwas tun

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

Aber ich möchte lieber nicht ...

ChrisMM
quelle
1
Sie könnten einstd::vector
idclev 463035818
std::vector::operator[]()kann Werte richtig ändern?
marvinIsSacul
@ ehemalsknownas_463035818 Bearbeitete Frage also keine Option;) Es ist eher eine theoretische Frage, aber ja, vectorwürde funktionieren.
ChrisMM
2
@marvinIsSacul sicher, aber std::vector::operator[]() constgibt eine constReferenz zurück
idclev 463035818
@ ChrisMM was ich erwartet hatte, wollte nur den Elefanten im Raum erwähnen :)
idclev 463035818

Antworten:

7

Zeiger verbreiten sich nicht const. Das Hinzufügen constzum Typ double*ergibt Ausbeuten double* const, die constbei Dereferenzierung zu einem Nichtwert führen.

Stattdessen können Sie Folgendes verwenden 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
};

oder ein eingebautes Array (nicht empfohlen):

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

Alle drei Optionen verbreiten sich const.

Wenn Sie wirklich Zeiger verwenden möchten (dringend nicht empfohlen), verwenden Sie mindestens a std::unique_ptr, um eine manuelle Speicherverwaltung zu vermeiden. Sie können den std::experimental::propagate_constWrapper aus den Grundlagen der Bibliothek verwenden. 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.
    // ...
};

Es ist noch nicht im Standard, aber viele Compiler unterstützen es. Natürlich ist dieser Ansatz den richtigen Behältern unterlegen.

LF
quelle
Der Versuch, dies zu tun, ohne den zugrunde liegenden Datentyp zu ändern, ist eher eine theoretische Frage als alles andere. Wenn nicht möglich, dann akzeptiere ich es als nicht möglich.
ChrisMM
@ ChrisMM Ich habe die Antwort mit einer Zeigerlösung aktualisiert. Aber warum :)
LF
"Warum" ist schwer zu beantworten, eher eine Neugier. "Eingebautes Array" oder std::arrayfunktioniert nicht, wenn Sie die Größe beim Kompilieren nicht kennen. vectorfügt Overhead hinzu; unique_ptrFügt keinen Overhead hinzu, aber wenn der Zeiger gemeinsam genutzt werden muss, benötigen Sie einen shared_ptrzusätzlichen Overhead. Ich glaube nicht, dass VS derzeit unterstützt propagate_const(zumindest die Header-Datei, auf die cppreference verweist, existiert nicht mit /std:c++latest) :(
ChrisMM
1
@ChrisMM Der Overhead von vectorTBH wird häufig überschätzt, insbesondere im Vergleich zum Aufwand der manuellen Speicherverwaltung. Wenn Sie die Zeiger manuell freigeben, müssen Sie außerdem einen Referenzzähler verwenden, damit der Overhead nicht besonders ist shared_ptr. Ich wusste nicht, dass VS noch nicht unterstützt propagate_const(GCC und Clang unterstützen beide IIRC), aber es ist nicht schwer, unsere eigenen gemäß der Spezifikation einzuführen.
LF
Ich bin damit einverstanden, dass der Overhead minimal ist, aber es gibt Gründe, Rohzeiger zu verwenden, wenn die Leistung kritisch ist (Speicher und Zeit). Ich benutze manchmal ein vectordann nehme seinen Inhalt über .data()oder &vec[0]und arbeite stattdessen direkt damit. Im Fall der Freigabe habe ich oft einen Besitzer des Zeigers, der erstellt und löscht, aber andere Klassen teilen die Daten.
ChrisMM