Como evitar modificações nos dados da matriz?

9

Digamos que eu tenha uma classe parecida com esta (este é apenas um exemplo):

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

O constconstrutor de cópia e a doNotChangeMyDatafunção fazem com que isso ptrnão possa ser alterado; no entanto, isso ainda me permite modificar o conteúdo da matriz apontada por ptr.

Existe uma maneira de impedir que o conteúdo da ptrmatriz seja alterado constapenas em instâncias, sem "ter cuidado" (ou mudar o ponteiro bruto)?

Eu sei que eu poderia fazer algo como

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

Mas eu prefiro não ter que ...

ChrisMM
fonte
11
você poderia usar umstd::vector
idclev 463035818
std::vector::operator[]()pode modificar valores certo?
MarvinIsSacul
@ formerlyknownas_463035818 Pergunta editada, portanto não é uma opção;) É mais uma questão teórica, mas sim, vectorfuncionaria.
ChrisMM
2
@marvinIsSacul certeza, mas std::vector::operator[]() constretorna uma constreferência
idclev 463035818
@ChrisMM o que eu esperava, só queria mencionar o elefante na sala :) #
idclev 463035818

Respostas:

7

Ponteiros não se propagam const. Adicionando constao tipo double*yield double* const, o que resulta em um não-valor constquando desreferenciado.

Em vez disso, você pode usar um 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 uma matriz incorporada (não recomendado):

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

Todas as três opções se propagam const.

Se você realmente deseja usar ponteiros (fortemente não recomendado), use pelo menos a std::unique_ptrpara evitar o gerenciamento manual de memória. Você pode usar o std::experimental::propagate_constwrapper nos fundamentos da biblioteca 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.
    // ...
};

Ainda não está no padrão, mas muitos compiladores o suportam. Obviamente, essa abordagem é inferior aos contêineres adequados.

LF
fonte
tentando fazer isso sem alterar o tipo de dados subjacente, mais uma questão teórica do que qualquer coisa. Se não for possível, aceitarei como não possível.
ChrisMM
@ChrisMM Atualizei a resposta com uma solução de ponteiro. Mas por que :) #
1012 LF
"Por que" é difícil de responder, mais uma curiosidade. "Matriz interna" ou std::arraynão funciona, se você não souber o tamanho no momento da compilação. vectoradiciona sobrecarga; unique_ptrnão adiciona sobrecarga, mas se o ponteiro precisar ser compartilhado, será necessário shared_ptradicionar sobrecarga. Eu não acho que o VS suporta atualmente propagate_const(pelo menos o arquivo de cabeçalho referido por cppreference não existe com /std:c++latest) :( #
ChrisMM
11
@ChrisMM A sobrecarga do vectorTBH é geralmente superestimada, especialmente se comparada ao esforço do gerenciamento manual de memória. Além disso, se você compartilhar os ponteiros manualmente, precisará usar uma contagem de referência, para que a sobrecarga não seja peculiar shared_ptr. Eu não sabia que o VS ainda não suporta propagate_const(o GCC e o Clang suportam o IIRC), mas não é difícil criar o nosso de acordo com as especificações.
LF
Concordo que a sobrecarga é mínima, mas há razões para usar ponteiros brutos quando o desempenho é crítico (memória e tempo). Às vezes eu uso um e vectorentão pego seu conteúdo via .data()ou &vec[0]e trabalho diretamente com ele. No caso do compartilhado, geralmente tenho um proprietário do ponteiro que cria e exclui, mas outras classes compartilham os dados.
ChrisMM