FAQ Lite
Precisão const

[ 18.1 ] O que é precisão const?
[ 18.2 ] Como precisão const se relaciona com segurança de tipos de dados normais?
[ 18.3 ] Devo proteger os itens com const tão logo seja possível, ou mais tarde?
[ 18.4 ] O que significa const Fred* p ?
[ 18.5 ] Qual é a diferença entre  const Fred* p, Fred* const p e
const Fred* const p ?
[ 18.6 ] O que significa const Fred& x ?
[ 18.7 ] Fred& const x faz algum sentido?
[ 18.8 ] O que significa Fred const& x ?
[ 18.9 ] O que é uma função-membro const ?
[ 18.10 ] O que eu preciso fazer se quero atualizar um dado membro invisível dentro de uma função membro const ?
[ 18.11 ] const_cast significa perda de oportunidades de otimização?
[ 18.12 ] Porque o compilador me permite alterar um int, após eu tê-lo apontado com um const int* ?
[ 18.13 ] const Fred* p significa que *p não pode ser alterado?

[ 18.1 ] O que é precisão const?

Uma boa coisa. Significa usar a palavra chave const para impedir que objetos const sejam modificados.

Por exemplo, se você quer criar uma função f() que receba um String, e adicionalmente você quer prometer aos que chamam essa função que não vai alterar o String passado para f(), você pode ter f() recebendo seu parâmetro String ...

Nos casos Pass by reference-to-const e Pass by pointer-to-const, qualquer tentativa de alterar o String do chamador dentro da função f() será flagrada pelo compilador como um erro em tempo de compilação. Essa verificação é feita inteiramente em tempo de compilação: não causa qualquer aumento de tamanho do programa ou perda de performance em tempo de execução. No caso Pass by value, a funçào chamada obtem uma cópia do String do chamador. Isso significa que f3() pode alterar sua cópia local, mas a cópia será destruida quando f3() retornar. Em particular, f3() não podem alterar o objeto String do chamador.

Um exemplo contrário. Se você quer criar uma função g() que aceita um String, mas você quer que os que chamam g() saibam que g() poderá alterar o objeto String do chamador. Nesse caso você pode ter g() recebendo seu parâmetro String ...

A ausência de const nessas funções diz ao compilador que elas tem permissão, embora não sejam obrigadas a isso, para alterar o objeto String do chamador. Assim, os chamadores podem passar o seu parâmetro String para qualquer das funções f(), mas apenas f3() (a que recebe parâmetros by value) pode passar seu String para g1() ou para g2(). Se f1() ou f2() precisam chamar qualquer das funções g(), uma cópia local do objeto String deve ser passada para a função g(); o parâmetro recebido por f1() ou por f2() não pode ser passado diretamente para qualquer das funções g(). Por exemplo:
    void g1(String& s);
    
    void f1(const String& s)
    {
      g1(s);          // Compile-time Error since s is const
    
      String localCopy = s;
      g1(localCopy);  // OK since localCopy is not const
    } 
Naturalmente, no caso acima, qualquer alteração que g1()fizer, será feita no objeto localCopy que é local para f1(). Em particular, nenhuma alteração será feita no parâmetro const que foi passado para f1()
Topo
[18.2] Como precisão const se relaciona com segurança de tipos de dados normais?

Declarar a constância de um parâmetro é apenas uma outra forma de dar segurança de tipos de dados. Pode-se dizer que uma const String, por exemplo, é de uma classe diferente de um String comum, já que a variante const está impedindo qualquer operação de modificação que podem ser realizadas com a variante não-const. Por exemplo, você pode imaginar que uma const String simplesmente não tem um operador de atribuição de conteúdo.

Se você já acha que a segurança de tipos comuns o ajuda a implementar um sistema correto (e isso é verdade, especialmente em grandes sistemas), você achará que proteção de const também o ajuda.

Topo
[18.3] Devo proteger os itens com  const tão logo seja possível, ou mais tarde?

No início, o mais cedo que for possível.

Pendências para corriigir os itens para const pode se tornar uma bola de neve: cada const que você adiciona aqui, requer que você adicione quantro outras ali e lá

Topo
[18.4] O que significa const Fred* p ?

Significa que p aponta para um objeto da classe Fred, mas p não pode ser usado para alterar aquele objeto Fred (naturalmente, p poderia também ser NULL)

Por exemplo, se a classe Fred tem uma função membro const denominada inspect(), dizer p->inspect() está certo. Mas se a classe Fred tem uma   função membro não-const denominada mutate(), dizer p->mutate() é um erro (o erro é pego pelo compilador, nenhum teste é feito em tempo de execução, o que significa que const não diminui a performance de seu programa)

Topo
[18.5] Qual é a diferença entre const Fred* p, Fred* const p e
const Fred* const p ?

Você deve ler as declarações de pointer da direita para a esquerda.
  • const Fred* p significa "p aponta para um Fred que deve ser tratado como uma const", ou seja, Fred é um objeto que não pode ser modificado via p.
  • Fred* const p significa "p é um pointer const para um Fred", ou seja, você pode alterar o objeto Fred via p, mas não pode alterar o próprio pointer p.
  • const Fred* const p significa "p é um pointer const para um const Fred", ou seja, você não pode alterar o objeto Fred via p, nem pode alterar o próprio pointer p
Topo
[18.6] O que significa const Fred& x ?

Significa "x é um alias de Fred, mas x não pode ser usado para alterar o objeto Fred.

Por exemplo, se a classe Fred tem uma função membro const denominada inspect(), dizer x.inspect() é Ok. Mas se a classe Fred tem uma função membro não-const denominada mutate(), dizer x.mutate() é um erro (o erro é pego pelo compilador, nenhum teste é feito em tempo de execução, o que significa que usar proteção de const não reduz a velocidade de execução de seu programa).

Topo
[18.7] Fred& const x faz algum sentido?

Não. Não não faz qualquer sentido.

Para descobrir o que a declaração acima significa, você deve lê-la da direita para a esquerda. Assim, Fred& const x significa "x é uma referência const para Fred". Mas essa declaração é redundante, já que referências são sempre const. Você não pode alterar o conteúdo de uma referência. Nunca. Com ou sem a especificação const explícita.

Em outras palavras, Fred& const x é funcionalmente equivalente a Fred& x. Já que você não melhora seu código em nada por acrescentar const após &, você não deve acrescentá-la para não confundir as pessoas que vão ler o seu código. Ou seja, o termo const vai fazer algumas pessoas pensarem  que Fred é uma const, como se você tivesse escrito "const Fred& x"

Topo
[18.8] O que significa Fred const& x ?

Fred const& x é funcionalmente equivalente a const Fred& x.

O problema com o uso de Fred const& x (com o const antes de &) é que essa forma pode ser digitada erradamente como Fred &const x, que é uma declaração sem sentido.

Melhor e mais simples usar const Fred& x.

Topo
[18.9] O que é uma função membro const ?

Uma função membro que inspeciona, ao invés de modificar, seu objeto.

Uma função membro const é indicada pelo sufixo const imediatamente após sua lista de parâmetros. Funções membro com um sufixo const são denominadas funções membros const ou inspetoras. Funções membro sem um sufixo const são denominadas funções membro não-const ou modificadoras.

class Fred {
    public:
      void inspect() const;   // This member promises NOT to change *this
      void mutate();          // This member function might change *this
    };
    
    void userCode(Fred& changeable, const Fred& unchangeable)
    {
      changeable.inspect();   // OK: doesn't change a changeable object
      changeable.mutate();    // OK: changes a changeable object
    
      unchangeable.inspect(); // OK: doesn't change an unchangeable object
      unchangeable.mutate();  // ERROR: attempt to change unchangeable object
    } 
O erro em unchangeable.mutate() é pego em tempo de compilação. const não gera qualquer custo em tempo de execução, seja de espaço, ou de velocidade de execução do programa.

O sufixo const na função membro inspect() significa que o estado abstrato (visível ao cliente) de um objeto não permite sua alteração. Isso é um pouco diferente de prometer que o conjunto de bits da struct de um objeto não vai ser alterado. Os compiladores C++ usualmente não conseguem realizar interpretação em nível de bit, a menos que consigam resolver os problemas de aliasing, os quais normalmente não podem ser resolvidos. Ou seja, pode existir uma alias não-const que possibilite alterar o estado do objeto.

Outro aspecto importante, e sutil, do uso de alias-name: apontar para um objeto com um pointer const não garante que o objeto não será alterado, garante apenas que o objeto não será alterado através daquele pointer.

Topo
[18.10] O que eu tenho que fazer se quero alterar um dado-membro invisível dentro de uma função membro const?

Use mutable ou use const_cast

Uma pequena percentagem das funções de inspeção precisam fazer alterações inofensivas em dados-membro. E.g. Um objeto set poderia querer armazenar sua última pesquisa, com vistas a melhorar a performance de sua próxima pesquisa. Ao dizer que alterações desse tipo são inócuas, quero dizer que essas alterações não tornam-se visíveis externamente ao objeto, a menos que a função-membro fosse uma função de alteração ao invés de uma função de inspeção.

Quando isso acontece, o dado-membro que pode ser modificado deve ser marcado como mutable (ponha a palavra-chave mutable imediatamente antes da declaração do dado-membro, ou seja, no mesmo lugar onde você poderia por const). Isso diz ao compilador que o dado-membro pode ser alterado durante a função-membro const. Se o seu compilador não suporta a palavra-chave mutable, você pode eliminar a constância de this usando a palavra-chave const_cast.

Por exemplo, em Set::lookup() const você poderia escrever

Set* self = const_cast<Set*>(this)

Após essa linha, self vai conter os mesmos bits que this (self == this), mas self é um Set* ao invés de um const set*, portanto você pode usar self para modificar um objeto apontado por this.

Topo
[18.11] const_cast significa perda de oportunidades de otimização?

Em teoria, sim. Na prática, não.

Ainda que a linguagem banisse const_cast, a única maneira de evitar a modificação de dados em cache ao longo de uma função-membro const seria resolver o problema de aliasing. Ou seja, provar que não existe um pointer não-const apontando para o objeto. Isso é possível apenas em casos muito raros, quando o objeto é construído dentro do escopo da invocação função-membro const, e quando todas as invocações a funções-membro não-const, entre a construção do objeto e a invocação da função-membro const, são estáticas, e quando todas essas invocações são também inline, e quando o próprio construtor é inline, e quando todas as funções-membro que o construtor chama são inline.

Topo
[18.12] Porque o compilador me permite alterar um int após eu tê-lo apontado com um const int*?

Porque "const int* p" significa "p promete não alterar o *p" não significa que "*p promete não se alterar"

Fazer um const int* apontar para um int não garante constância ao int. O int não pode ser alterado via o const int*, mas se alguém tem um int* (note: não-const) que aponta para um alias do mesmo int, então int* pode ser usado para alterar o int. Por exemplo:

 void f(const int* p1, int* p2)
    {
      int i = *p1;         // Get the (original) value of *p1
      *p2 = 7;             // If p1 == p2, this will also change *p1
      int j = *p1;         // Get the (possibly new) value of *p1
      if (i != j) {
        cout << "*p1 changed, but it didn't change via pointer p1!\n";
        assert(p1 == p2);  // This is the only way *p1 could be different
      }
    }
    
    main()
    {
      int x;
      f(&x, &x);           // This is perfectly legal (and even moral!)
    } 
Note que main() e f(const int* int) poderiam estar em diferentes unidades de compilação, que foram compiladas em diferentes dias da semana. Nesse casos não há como o compilador detectar a existência de alias em tempo de compilação. Portanto não há como se definir uma regra de linguagem que proíba esse tipo de coisa. De fato, não é desejável que exista tal regra, uma vez que em geral se considera um recurso da linguagem a possibilidade de se ter vários pointers apontando para a mesma coisa. O fato de um dos pointer prometer não alterar a coisa apontada, é uma promessa feita pelo pointer e não em nome da coisa que ele aponta.
Topo
[18.13 ] const Fred* p significa que *p não pode ser alterado?

Não! Para melhor entendimento, leia a FAQ anterior.

const Fred* p significa que Fred não pode ser alterado via pointer p, mas qualquer outro pointer alias que não seja const pode ser usado para alterar o objeto Fred. Por exemplo, se você tem dois pointers const Fred* p e Fred* q que apontam para o mesmo objeto Fred, pointer q pode ser usado para alterar o objeto Fred, embora o pointer p não o possa.

    class Fred {
    public:
      void inspect() const;   // A const member function
      void mutate();          // A non-const member function
    };
    
    main()
    {
      Fred f;
      const Fred* p = &f;
            Fred* q = &f;
    
      p->inspect();    // OK: No change to *p
      p->mutate();     // Error: Can't change *p via p
    
      q->inspect();    // OK: q is allowed to inspect the object
      q->mutate();     // OK: q is allowed to mutate the object
    } 
Topo Anterior Próximo Índice
C++ FAQ Lite
Copyright © 1991-98 by Marshall Cline Ph.D., cline@parashift.com
Tradução: Dagoberto Haele Arnaut

| Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site |