¿Cómo evitar la modificación de los datos de la matriz?

9

Digamos que tengo una clase que se ve así (esto es solo un ejemplo):

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

El consttanto en el constructor de copia y la doNotChangeMyDatafunción de hacerlo de modo que ptrno se puede cambiar; sin embargo, esto todavía me permite modificar el contenido de la matriz a la que apunta ptr.

¿Hay alguna manera de evitar que el contenido de ptrla matriz se constmodifique solo en instancias, salvo "tener cuidado" (o cambiar el puntero sin formato)?

Sé que podría hacer algo como

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

Pero prefiero no tener que ...

ChrisMM
fuente
1
se puede utilizar unstd::vector
IDCLEV 463035818
std::vector::operator[]()puede modificar los valores ¿verdad?
marvinIsSacul
@ formerlyknownas_463035818 Pregunta editada, así que no es una opción;) Es más una pregunta teórica, pero sí, vectorfuncionaría.
ChrisMM
2
@marvinIsSacul seguro, pero std::vector::operator[]() constdevuelve una constreferencia
idclev 463035818
@ChrisMM lo que esperaba, solo quería mencionar al elefante en la habitación :)
idclev 463035818

Respuestas:

7

Los punteros no se propagan const. Agregar constal tipo double*rendimientos double* const, lo que resulta en un valor no constl cuando se desreferencia.

En cambio, puede usar un 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
};

o una matriz integrada (no recomendado):

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

Las tres opciones se propagan const.

Si realmente desea utilizar punteros (no se recomienda encarecidamente), al menos utilice a std::unique_ptrpara evitar la gestión manual de la memoria. Puede usar el std::experimental::propagate_constcontenedor de la biblioteca fundamentos 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.
    // ...
};

Todavía no está en el estándar, pero muchos compiladores lo admiten. Por supuesto, este enfoque es inferior a los contenedores adecuados.

LF
fuente
tratando de hacer esto sin cambiar el tipo de datos subyacente, más una pregunta teórica que otra cosa. Si no es posible, lo aceptaré como no posible.
ChrisMM
@ChrisMM He actualizado la respuesta con una solución de puntero. Pero por qué :)
LF
"Por qué" es difícil de responder, más una curiosidad. "Matriz integrada" o std::arrayno funciona, si no conoce el tamaño en el momento de la compilación. vectoragrega gastos generales; unique_ptrno agrega sobrecarga, pero si el puntero necesita ser compartido, entonces necesita shared_ptrlo que agrega sobrecarga. No creo que VS sea compatible actualmente propagate_const(al menos el archivo de encabezado al que hace referencia cppreference no existe /std:c++latest) :(
ChrisMM
1
@ChrisMM La sobrecarga de vectorTBH a menudo se sobreestima, especialmente en comparación con el esfuerzo de la gestión manual de la memoria. Además, si comparte los punteros manualmente, debe usar un recuento de referencia, por lo que la sobrecarga no es peculiar shared_ptr. No sabía que VS aún no es compatible propagate_const(GCC y Clang son compatibles con IIRC), pero no es difícil implementar el nuestro según las especificaciones.
LF
Estoy de acuerdo en que la sobrecarga es mínima, pero hay razones para usar punteros sin procesar cuando el rendimiento es crítico (memoria y tiempo). A veces uso un y vectorluego tomo su contenido a través de .data()o &vec[0]y trabajo directamente con eso. En el caso de lo compartido, a menudo tengo un propietario del puntero que crea y elimina, pero otras clases comparten los datos.
ChrisMM