Jak zapobiec modyfikacji danych tablicowych?

9

Powiedzmy, że mam klasę, która wygląda tak (to tylko przykład):

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

Zarówno constw konstruktorze kopiowania, jak i doNotChangeMyDatafunkcji sprawiają, że ptrnie można go zmienić; jednak nadal pozwala mi to modyfikować zawartość tablicy, na którą wskazuje ptr.

Czy istnieje sposób, aby zapobiec ptrzmianie zawartości tablicy consttylko w instancjach, poza „ostrożnością” (lub odejściem od surowego wskaźnika)?

Wiem, że mógłbym zrobić coś takiego

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

Ale wolałbym nie musieć ...

ChrisMM
źródło
1
możesz użyćstd::vector
idclev 463035818
std::vector::operator[]()czy mogę modyfikować wartości, prawda?
marvinIsSacul,
@ previouslyknownas_463035818 Edytowane pytanie, więc nie ma opcji;) To pytanie bardziej teoretyczne, ale tak, vectorzadziałałoby.
ChrisMM,
2
@marvinIsSacul na pewno, ale std::vector::operator[]() constzwraca constreferencję
idclev 463035818,
@ChrisMM, czego się spodziewałem, chciałem tylko wspomnieć o słoniu w pokoju :)
idclev 463035818,

Odpowiedzi:

7

Wskaźniki nie rozprzestrzeniają się const. Dodanie constdo typu double*daje wynik double* const, który powoduje brak wartości constpo dereferencji.

Zamiast tego możesz użyć 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
};

lub wbudowana tablica (niezalecane):

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

Wszystkie trzy opcje są propagowane const.

Jeśli naprawdę chcesz używać wskaźników (zdecydowanie nie zalecane), przynajmniej użyj, std::unique_ptraby uniknąć ręcznego zarządzania pamięcią. Możesz użyć std::experimental::propagate_constopakowania z podstaw biblioteki 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.
    // ...
};

Nie ma go jeszcze w standardzie, ale obsługuje go wiele kompilatorów. Oczywiście takie podejście jest gorsze od odpowiednich pojemników.

LF
źródło
próbując to zrobić bez zmiany podstawowego typu danych, bardziej teoretyczne pytanie niż cokolwiek innego. Jeśli nie jest to możliwe, zaakceptuję to jako niemożliwe.
ChrisMM,
@ChrisMM Zaktualizowałem odpowiedź za pomocą wskaźnika. Ale dlaczego :)
LF
„Dlaczego” trudno jest odpowiedzieć, bardziej ciekawość. „Wbudowana tablica” lub std::arraynie działa, jeśli nie znasz rozmiaru w czasie kompilacji. vectordodaje koszty ogólne; unique_ptrnie dodaje narzutu, ale jeśli wskaźnik musi być współużytkowany, to potrzebujesz, shared_ptrktóry dodaje narzut. Nie sądzę, że VS obecnie obsługuje propagate_const(przynajmniej plik nagłówka, do którego odwołuje się cppreference, nie istnieje /std:c++latest) :(
ChrisMM
1
@ChrisMM Narzut vectorjest często zawyżony TBH, szczególnie w porównaniu z wysiłkiem ręcznego zarządzania pamięcią. Ponadto, jeśli współdzielisz wskaźniki ręcznie, musisz użyć liczby referencyjnej, aby narzut nie był specyficzny shared_ptr. Nie wiedziałem, że VS propagate_constjeszcze nie obsługuje (zarówno GCC, jak i Clang obsługują IIRC), ale nie jest trudno wdrożyć własne zgodnie ze specyfikacją.
LF,
Zgadzam się, że narzut jest minimalny, ale istnieją powody, aby używać surowych wskaźników, gdy wydajność jest krytyczna (pamięć i czas). Czasami używam a vectornastępnie biorę jego zawartość przez .data()lub &vec[0]i bezpośrednio z tym pracuję. W przypadku współdzielonego często mam jednego właściciela wskaźnika, który tworzy i usuwa, ale inne klasy współużytkują dane.
ChrisMM,