Quantos e quais são os usos de "const" em C ++?

129

Como programador iniciante em C ++, existem algumas construções que ainda parecem muito obscuras para mim, uma delas é const. Você pode usá-lo em tantos lugares e com tantos efeitos diferentes que é quase impossível para um iniciante sair vivo. Algum guru de C ++ explicará uma vez para sempre os vários usos e se e / ou por que não usá-los?

tunnuz
fonte
exatamente procurando por essa pergunta: D
alamin

Respostas:

100

Tentando coletar alguns usos:

Vinculando alguns temporários à referência à const, para prolongar sua vida útil. A referência pode ser uma base - e o destruidor dela não precisa ser virtual - o destruidor certo ainda é chamado:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Explicação , usando o código:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Esse truque é usado na classe de utilitário ScopeGuard do Alexandrescu. Depois que o temporário fica fora do escopo, o destruidor de Derivado é chamado corretamente. O código acima perde alguns pequenos detalhes, mas esse é o grande problema.


Use const para dizer aos outros que os métodos não mudarão o estado lógico desse objeto.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Use const para copiar-na-gravar classes , para fazer o compilador ajudá-lo a decidir quando e quando você não precisa copiar.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Explicação : Você pode compartilhar dados quando copiar algo, desde que os dados do objeto original e do objeto copiado permaneçam os mesmos. Depois que um dos objetos altera os dados, você precisa agora de duas versões: uma para o original e outra para a cópia. Ou seja, você copia em uma gravação para qualquer um dos objetos, para que agora ambos tenham sua própria versão.

Usando código :

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

O trecho acima imprime o mesmo endereço no meu GCC, porque a biblioteca C ++ usada implementa uma cópia na gravação std::string. As duas cadeias, mesmo que sejam objetos distintos, compartilham a mesma memória para seus dados. Tornar bnão-const preferirá a versão não-const do operator[]e o GCC criará uma cópia do buffer de memória de backup, porque podemos alterá-lo e não deve afetar os dados de a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Para que o construtor de cópias faça cópias de objetos const e temporários :

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Por fazer constantes que trivialmente não podem mudar

double const PI = 3.1415;

Para passar objetos arbitrários por referência em vez de por valor - para impedir a passagem por valor possivelmente cara ou impossível

void PrintIt(Object const& obj) {
    // ...
}
Johannes Schaub - litb
fonte
2
Você pode explicar por favor o primeiro e o terceiro uso em seus exemplos?
tunnuz
"Por garantir que o parâmetro não possa ser NULL", não vejo como const tem algo a ver com esse exemplo.
Logan Capaldo
Opa, eu falhei. de alguma forma comecei a escrever sobre referências. muito obrigado por gemendo :) eu vou, naturalmente, remover esse material agora :)
Johannes Schaub - litb
3
Por favor, explique o primeiro exemplo. Não faz muito sentido para mim.
Chikuba # 30/12
28

Existem realmente 2 usos principais de const em C ++.

Valores Const

Se um valor estiver na forma de uma variável, membro ou parâmetro que não será (ou não deveria) ser alterado durante sua vida útil, marque-o como const. Isso ajuda a evitar mutações no objeto. Por exemplo, na função a seguir, não preciso alterar a instância do Student aprovada, marcando-a como const.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

Por que você faria isso. É muito mais fácil argumentar sobre um algoritmo se você souber que os dados subjacentes não podem mudar. "const" ajuda, mas não garante que isso será alcançado.

Obviamente, imprimir dados para cout não requer muita reflexão :)

Marcando um método de membro como const

No exemplo anterior, marquei Student como const. Mas como o C ++ sabia que chamar o método GetName () no aluno não mudaria o objeto? A resposta é que o método foi marcado como const.

class Student {
  public:
    string GetName() const { ... }
};

A marcação de um método "const" faz duas coisas. Principalmente, ele diz ao C ++ que esse método não muda o meu objeto. A segunda coisa é que todas as variáveis ​​de membro agora serão tratadas como se fossem marcadas como const. Isso ajuda, mas não impede que você modifique a instância da sua classe.

Este é um exemplo extremamente simples, mas espero que ajude a responder suas perguntas.

JaredPar
fonte
16

Tome cuidado para entender a diferença entre estas 4 declarações:

As 2 declarações a seguir são idênticas semanticamente. Você pode mudar para onde ccp1 e ccp2 apontam, mas não pode mudar para o que eles apontam.

const char* ccp1;
char const* ccp2;

Em seguida, o ponteiro é const; portanto, para ser significativo, ele deve ser inicializado para apontar para alguma coisa. Você não pode apontar para outra coisa, no entanto, a coisa para a qual aponta pode ser alterada.

char* const cpc = &something_possibly_not_const;

Finalmente, combinamos os dois - para que a coisa apontada não possa ser modificada e o ponteiro não possa apontar para outro lugar.

const char* const ccpc = &const_obj;

A regra espiral no sentido horário pode ajudar a desembaraçar uma declaração http://c-faq.com/decl/spiral.anderson.html

Steve Folly
fonte
De uma maneira indireta, sim! A regra da espiral no sentido horário descreve melhor - comece pelo nome (kpPointer) e desenhe uma espiral no sentido horário saindo pelo token e diga cada token. Obviamente, não há nada à direita do kpPointer, mas ele ainda funciona.
Steve Folly
3

Como uma pequena nota, como li aqui , é útil notar que

const aplica-se ao que estiver à sua esquerda imediata (exceto se não houver nada; nesse caso, aplica-se ao que é seu direito imediato).

JoePerkins
fonte