Как предотвратить изменение массива данных?

9

Скажем, у меня есть класс, который выглядит следующим образом (это только пример):

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

И constв конструкторе копирования, и в doNotChangeMyDataфункции делают его таким, чтобы ptrего нельзя было изменить; однако, это все еще позволяет мне изменять содержимое массива, на который указывает ptr.

Есть ли способ предотвратить изменение содержимого ptrмассива только в constэкземплярах, за исключением «осторожности» (или отказа от необработанного указателя)?

Я знаю, что могу сделать что-то вроде

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

Но я бы не хотел ...

ChrisMM
источник
1
Вы могли бы использоватьstd::vector
idclev 463035818
std::vector::operator[]()можно изменить значения правильно?
marvinIsSacul
@ прежний известныйas_463035818 Отредактированный вопрос так не вариант;) Это скорее теоретический вопрос, но да, vectorбудет работать.
ChrisMM
2
@marvinIsSacul конечно, но std::vector::operator[]() constвозвращает constссылку
idclev 463035818
@ChrisMM то, что я ожидал, просто хотел упомянуть слона в комнате :)
idclev 463035818

Ответы:

7

Указатели не размножаются const. Добавление constк типу double*приводит double* constк появлению constненулевого значения при разыменовании.

Вместо этого вы можете использовать std::vector:

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

а std::array:

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

или встроенный массив (не рекомендуется):

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

Все три варианта размножаются const.

Если вы действительно хотите использовать указатели (настоятельно не рекомендуется), хотя бы используйте a, std::unique_ptrчтобы избежать ручного управления памятью. Вы можете использовать std::experimental::propagate_constобертку из библиотеки основы 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.
    // ...
};

Его пока нет в стандарте, но многие компиляторы его поддерживают. Конечно, этот подход уступает надлежащим контейнерам.

LF
источник
Попытка сделать это без изменения базового типа данных, более теоретический вопрос, чем что-либо еще. Если это невозможно, я приму это как невозможное.
ChrisMM
@ChrisMM Я обновил ответ решением для указателя. Но почему :)
LF
«Почему» трудно ответить, больше любопытство. «Встроенный массив» или std::arrayне работает, если вы не знаете размер во время компиляции. vectorдобавляет накладные расходы; unique_ptrне добавляет накладных расходов, но если указатель должен использоваться совместно, то вам нужно, shared_ptrчто добавляет накладные расходы. Я не думаю, что VS в настоящее время поддерживает propagate_const(по крайней мере, заголовочный файл, на который ссылается cppreference, не существует /std:c++latest) :(
ChrisMM
1
@ChrisMM Затраты на vectorТБХ часто завышаются, особенно по сравнению с ручным управлением памятью. Кроме того, если вы делитесь указателями вручную, вы должны использовать счетчик ссылок, так что накладные расходы не свойственны shared_ptr. Я не знал, что VS еще не поддерживает propagate_const(GCC и Clang поддерживают IIRC), но нетрудно развернуть нашу собственную согласно спецификации.
LF
Я согласен, что накладные расходы минимальны, но есть причины использовать необработанные указатели, когда производительность критична (память и время). Я иногда использую, а vectorзатем беру его содержимое через .data()или &vec[0]и работаю напрямую с этим. В случае общего доступа у меня часто есть один владелец указателя, который создает и удаляет, но другие классы совместно используют данные.
ChrisMM