Norma C++
- const exatidão
- o que é “exatidão const”?
- como é que o” const correctness ” está relacionado com a segurança normal do tipo?
- o que significa” const X* p”?
- What’s the difference between “const X* p”, “X* const p” and “const X* const p”?
- o que significa” const X& x”?
- What do “X const& x” and “X const * p” mean?
- What is a”const member function”?
- Qual é a relação entre uma função return-by-reference e uma função de membro const?
- Qual é o problema com “const-overloading”?
- como pode ajudar-me a conceber melhores classes se eu distinguir o estado lógico do estado físico?
- a constância das minhas funções de membro público deve basear-se no que o método faz ao estado lógico do objecto, ou ao estado físico?
- What do I do if I want a const member function to make an “invisible” change to a data member?
- por que o compilador me permite mudar um int depois de eu ter apontado para ele com um const int*?
- Why am I getting an error converting a Foo * * → const Foo**?
const exatidão
o que é “exatidão const”?
uma coisa boa. Significa usar a palavra-chave const
para evitar que objetos const
sofram mutação.
Por exemplo, se você quiser criar uma função f()
que aceitou um std::string
, e você deseja a promessa callersnot para alterar a chamada std::string
que é passada para f()
, você pode ter f()
receber o seu std::string
parâmetro…
-
void f1(const std::string& s);
// Passagem por referência para-const
-
void f2(const std::string* sptr);
// Passe pelo ponteiro-para-const
-
void f3(std::string s);
// Passagem por valor
Na passagem por referência-para-const
e passe pelo ponteiro-para-const
casos, qualquer tentativa de alterar a chamadastd::string
dentro do f()
funções seriam sinalizados pelo compilador como um erro na tempo de compilação. Esta verificação é feita inteiramente no tempo de compilação: não existe espaço de tempo de execução ou custo de velocidade para o const
. In the pass by value case (f3()
), the called function gets a copy of the caller’s std::string
. Isto significa que f3()
pode alterar a sua localização, mas a cópia é destruída quando f3()
retorna. In particular f3()
cannot change the caller’s std::string
object.
como um exemplo oposto, suponha que você queria criar uma função g()
que aceitasse um std::string
, mas você quer que os chamadores saibam que g()
pode mudar o objeto do chamador std::string
. Neste caso, você pode ter g()
receber o seustd::string
parâmetro…
-
void g1(std::string& s);
// Passagem por referência a não-const
-
void g2(std::string* sptr);
// Passe pelo ponteiro-para-não-const
A falta do const
nessas funções informa ao compilador que é permitido (mas não obrigatório) alterar thecaller do std::string
objeto. Assim, eles podem passar seus std::string
para qualquer uma das funções f()
, mas apenas f3()
(aquele que recebe seu parâmetro “por valor”) pode passar seus std::string
para g1()
ou g2()
. Se f1()
ou f2()
precisam chamar a função g()
, uma cópia local do objeto std::string
deve ser passada para a função g()
; o parâmetro para f1()
ou f2()
não pode ser diretamente passada para a função g()
. E. g.,
void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}
Naturalmente, no caso acima, quaisquer alterações que g1()
faz com que são feitas para o localCopy
objeto que é o local para f1()
.Em particular, não serão efectuadas alterações para o const
parâmetro que foi passado por referência para f1()
.
como é que o” const correctness ” está relacionado com a segurança normal do tipo?
declarar a const
– nidade de um parâmetro é apenas outra forma de segurança do tipo.
se achar que a segurança normal do tipo o ajuda a corrigir os sistemas (ele faz; especialmente em sistemas grandes), encontraráconst
a correcção também ajuda.
o benefício da correcção de const
é que o impede de alterar inadvertidamente algo que não esperava que fosse modificado. Você acaba precisando decorar seu código com algumas teclas extras (a palavra— chave const
), com a vantagem de que você está dizendo ao compilador e outros programadores alguma parte adicional de informações semânticas importantes-informações que o compilador usa para prevenir erros e outros programadores usam como documentação.
conceptualmente você pode imaginar que const std::string
, por exemplo, é uma classe diferente do normal std::string
, uma vez que a varianteconst
está conceptualmente faltando as várias operações mutantes que estão disponíveis na variante não- const
. Por exemplo, você pode imaginar conceptualmente que um const std::string
simplesmente não tem um operador de atribuição+=
ou qualquer outra operação mutante.Devo tentar que as coisas estejam corretas “mais cedo “ou”mais tarde”?
no início muito, muito, muito.
back-patching const
correctness results in a snowball effect: every const
you add “over here” requires four moreto be added ” over there.”
Add const
early and often.
o que significa” const X* p”?
significa p
aponta para um objeto da classe X
, mas p
não pode ser usado para mudar que X
objeto (naturalmente p
poderia ser NULL
).
leia-o da direita para a esquerda: “p é um ponteiro para um X que é constante.”
por exemplo, se a classe X
tem uma função de membro const
tal como inspect() const
, pode-se dizerp->inspect()
. Mas se a classe X
tem uma função não – const
membro chamada mutate()
, é anerror se você diz p->mutate()
.Significativamente, este erro é pego pelo compilador em tempo de compilação-nenhum teste de tempo de execução é feito. Isso significa que const
não abranda o seu programa e não requer que você escreva casos de teste extra para verificar as coisas em tempo de execução — o compilador faz o trabalho em tempo de compilação.
What’s the difference between “const X* p”, “X* const p” and “const X* const p”?
leia as declarações de ponteiro da direita para a esquerda.
-
const X* p
significa ”p
aponta para umX
que éconst
“: o objetoX
não pode ser alterado viap
. -
X* const p
significa ”p
é umconst
ponto para umX
que não éconst
“: você não pode mudar o ponteirop
em si, mas você pode mudar o objetoX
viap
. -
const X* const p
significa ”p
é umconst
ponteiro para umX
que éconst
“: você não pode mudar o ponteirop
em si, nem pode mudar o objetoX
viap
.E, oh sim, eu mencionei para ler as suas declarações de ponteiro da direita para a esquerda?
o que significa” const X& x”?
significa
x
pseudónimos de objetoX
, mas você não pode mudar issoX
objeto viax
.leia-o da direita para a esquerda: “
x
é uma referência a umX
que éconst
.”por exemplo, se a classe
X
tem uma função de membroconst
tal comoinspect() const
, pode-se dizerx.inspect()
. Mas se a classeX
tem uma função não –const
membro chamadomutate()
, é um erro você dizx.mutate()
.Isso é totalmente simétrico, com ponteiros para const, incluindo o fato de que o compilador faz toda a verificação em tempo de compilação, o que significa
const
não abrandar o seu programa e não requer que você escreva teste extra-casos para verificar as coisas em tempo de execução.What do “X const& x” and “X const * p” mean?
X const& x
é equivalente aconst X& x
, eX const* x
é equivalente aconst X* x
.algumas pessoas preferem o estilo
const
– on-the-right, chamando-o de “consistenteconst
“ou, usando um termo cunhado por Simon Brand,” Eastconst
. Na verdade, o estilo “Lesteconst
” pode ser mais consistente do que a alternativa: o estilo “Lesteconst
” alwaysputs oconst
à direita do que consente, enquanto o outro estilo às vezes coloca oconst
à esquerda e às vezes à direita (para declarações de ponteiros e funções de membroconst
).com o estilo ” Leste
const
“, uma variável local que éconst
é definida com oconst
à direita:int const a = 42;
. Similarmente uma variávelstatic
que éconst
é definida comostatic double const x = 3.14;
.Basicamente cadaconst
termina à direita da coisa que consente, incluindo oconst
que é necessário para estar à direita:const
declarações de ponteiro e com uma funçãoconst
membro.o estilo” Leste
const
” também é menos confuso quando usado com pseudónimos tipo: Por quefoo
ebar
têm diferentes tipos aqui?using X_ptr = X*;const X_ptr foo;const X* bar;
usando o estilo” este
const
” torna isto mais claro:using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;
Ele é mais claro aqui que
foo
efoobar
são do mesmo tipo e quebar
é um tipo diferente.o estilo” Leste
const
” também é mais consistente com as declarações de ponteiro. Contrastar o estilo tradicional:const X** foo;const X* const* bar;const X* const* const baz;
com o estilo” East “
const
“X const** foo;X const* const* bar;X const* const* const baz;
apesar destes benefícios, o estilo “East”
const
-on-the-right ainda não é popular, então o código legado tende a ter o estilo tradicional.”X” & const x ” faz algum sentido?Não, é um disparate.para saber o que significa a declaração acima, leia-a da direita para a esquerda: “
x
é uma referência aX
“. Mas isso é redundante-as referências são sempreconst
, no sentido de que você nunca pode reaquecer areference para fazê-lo se referir a um objeto diferente. Nunca. Com ou semconst
.por outras palavras, “
X& const x
“é funcionalmente equivalente a”X& x
“. Uma vez que você não está ganhando nada adicionando oconst
após o&
, você não deve adicioná — lo: ele vai confundir as pessoas-oconst
vai fazer algumas pessoas pensar que oX
éconst
, como se você tivesse dito “const X& x
“.What is a”const member function”?
uma função membro que inspecciona (em vez de sofrer mutações) o seu objecto.
a função de membro
const
é indicada por um sufixoconst
logo após a lista de parâmetros da função de membro. As funções dos membros com um sufixoconst
são chamadas “const
funções dos Membros ” ou “inspectores”.”Funções membros sem um sufixoconst
são chamadas” funções não –const
membros “ou” mutantes.”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(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}
a tentativa de chamar
unchangeable.mutate()
é um erro pego no tempo de compilação. Não há espaço de tempo de execução ou speedpenalty paraconst
, e você não precisa escrever casos de teste para verificá-lo no tempo de execução.deve utilizar-se a função de membro que segue
const
eminspect()
, para que o método não altere o estado do objecto (visível pelo cliente). Isso é ligeiramente diferente de dizer que o método não vai mudar os “bits brutos” do objetostruct
. Compiladores de C++ não são autorizados a tomar a interpretação “bitwise” a menos que eles possam resolver o problema do processo dealiasing, que normalmente não pode ser resolvido (ou seja, um não-const
alias poderia existir que poderia modificar o estado do objeto). Outra (importante) visão desta questão de alusão: apontar para um objeto com um ponteiro-para-const
não garante que o objeto não vai mudar; ele apenas promete que o objeto não vai mudar através desse ponteiro.Qual é a relação entre uma função return-by-reference e uma função de membro const?
se quiser devolver um membro do seu objecto
this
por referência de um método de inspector, deve devolvê-lo utilizando o reference-to-const (const X& inspect() const
) ou por valor (X inspect() const
).class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}
a boa notícia é que o compilador irá frequentemente apanhá-lo se se enganar. Em particular, se você acidentalmente reverter um membro do seu objeto
this
por referência nãoconst
, como emPerson::name_evil()
acima, o compilador irá frequentemente detectá-lo e dar-lhe um erro de tempo de compilação ao compilar as entranhas de, Neste caso,Person::name_evil()
.a má notícia é que o compilador nem sempre o pegará: há alguns casos em que o compilador simplesmente não lhe dará uma mensagem de erro de tempo de compilação.Tradução: você precisa pensar. Se isso o assusta, encontre outra linha de trabalho; “pensar” não é uma palavra de quatro letras.
lembre-se da “filosofia” espalhada ao longo desta secção: uma função de Membro não deve mudar (ou permitir que um ouvinte mude) o estado lógico do objecto
this
(também conhecido como estado abstracto ou estado significante). Pense no que um objeto significa, não como ele é implementado internamente. A idade e o nome de uma pessoa são partes lógicas da pessoa, mas o vizinho e empregador da pessoa não são. Um método de inspetor que retorna parte do estado lógico / abstrato / sentido do objetothis
não deve retornar um não-const
ponteiro (ou referência) para essa parte,independente de se essa parte é internamente implementada como um membro de dados direto fisicamente embutido dentro do objetothis
ou de alguma outra forma.Qual é o problema com “const-overloading”?
const
sobrecarga ajuda a atingirconst
correcção.const
sobrecarga é quando se tem um método de inspecção e um método de mutação com o mesmo nome e o mesmo número e tipos de parâmetros. Os dois métodos distintos diferem apenas em que o espectador éconst
e o mutador não éconst
.o uso mais comum de
const
sobrecarga é com o operador subescrito. Você deve geralmente tentar usar um dos modelos de contêineres do thestandard, comostd::vector
, mas se você precisar criar sua própria classe que tem um subscriptoperador, aqui está a regra geral: os operadores subscript muitas vezes vêm em pares.class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};
o operador
const
subscript devolve umaconst
-referência, de modo que o compilador impedirá os utilizadores de cortarem inadvertidamente/alterarem oFred
. O operador não –const
subscript devolve uma referência não –const
, que é a sua forma de rotular os seus chamadores (e o compilador) que os seus chamadores podem modificar o objectoFred
.When a user of your
MyFredList
class calls the subscript operator, the compiler selects which overload to call basedon the constness of theirMyFredList
. Se o chamador tiver umMyFredList a
ouMyFredList& a
, entãoa
vai callthe nãoconst
subscrito operador, e o chamador irá acabar com um não-const
referência a umaFred
:Por exemplo, suponha
class Fred
tem um inspector-métodoinspect() const
e um mutator methodmutate()
:void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}
no Entanto, se o chamador tiver um
const MyFredList a
ouconst MyFredList& a
, entãoa
vai chamar aconst
subscriptoperator, e o chamador irá terminar com umconst
referência a umaFred
. Isso permite que o chamador para inspecionar oFred
ema
, mas impede que o chamador, inadvertidamente, transformando-a/alterar oFred
ema
.void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}
cont overloading for subscript-and funcall-operators is illustrated here, here, here, and here.
você pode, naturalmente, também usar
const
– sobrecarga para outras coisas que não o operador subscript.como pode ajudar-me a conceber melhores classes se eu distinguir o estado lógico do estado físico?
porque isso o incentiva a projetar suas aulas de fora para dentro e não de dentro para fora, o que, por sua vez, torna suas classes e objetos mais fáceis de entender e usar, mais intuitivos, menos propensos a erros, e mais rápido. Isso é uma simplificação excessiva. Para entender todos os “se”, ” se ” e “mas”, terá de ler o resto da resposta!)
vamos entender isso de dentro para fora-você (deve) projetar suas aulas do lado de fora, mas se você é novo para este conceito, é mais fácil de entender a partir do lado de fora.
no interior, os seus objectos têm estado físico (ou concreto ou pontiagudo). Este é o estado que é fácil para os programadores ver e entender; é o estado que estaria lá se a classe fosse apenas um estilo C
struct
.no exterior, os seus objectos têm utilizadores da sua classe, e estes utilizadores estão limitados a utilizar apenas
public
funções de membros efriend
S. Estes usuários externos também percebem o objeto como tendo estado, por exemplo, se o objeto é da classeRectangle
com métodoswidth()
,height()
earea()
, seus usuários diriam que esses três são todos parte do estado lógico (ou abstrato ou sentido) do objeto. Para um utilizador externo, oRectangle
objectactualmente tem uma área, mesmo que essa área seja calculada em tempo real (por exemplo, se o métodoarea()
devolve o produto da largura e altura do objecto). Na verdade, e este é o ponto importante, seus usuários não sabem e não se importam como você implementa qualquer um destes métodos; seus usuários ainda percebem, a partir de sua perspectiva, que seu objeto logicamente tem um estado ameaningwise de largura, altura e área.o exemplo
area()
mostra um caso em que o estado lógico pode conter elementos que não são diretamente realizados no estado físico. O oposto também é verdadeiro: classes às vezes intencionalmente esconder parte do estado físico(Concreto, bitwise) de seus objetos dos usuários — eles intencionalmente não fornecem quaisquer funçõespublic
membros oufriend
s que permitiriam aos usuários ler ou escrever ou até mesmo saber sobre este estado oculto. Isso significa que há aberturas no estado físico do objeto que não têm elementos correspondentes no estado lógico do objeto.como exemplo deste último caso, um objecto de colecção pode guardar a sua última pesquisa na esperança de melhorar o desempenho da sua próxima pesquisa. Este cache é certamente parte do estado físico do objeto, mas lá está um detalhe de implementação interna que provavelmente não será exposto aos usuários — provavelmente não será parte do estado slógico do objeto. Dizer o que é fácil se você pensa de fora para dentro: se os usuários do collection-object têm noway para verificar o estado do cache em si, então o cache é transparente, e não faz parte do estado logical do objeto.
a constância das minhas funções de membro público deve basear-se no que o método faz ao estado lógico do objecto, ou ao estado físico?
lógico.Não há maneira de facilitar a próxima parte. Vai doer. A melhor recomendação é sentar-se. E por favor, para vossa segurança, certifiquem-se de que não há instrumentos afiados por perto.
vamos voltar para o exemplo Coleção-objeto. Lembrar: há um método de pesquisa que analisa a última pesquisa na esperança de acelerar futuras pesquisas.
vamos afirmar o que é provavelmente óbvio: assumir que o método de pesquisa não faz alterações a qualquer estado lógico do objeto de coleta.Então … chegou a hora de te magoar. Estás pronto?
aqui vem: se o método de pesquisa não faz nenhuma alteração a qualquer estado lógico do objeto de coleção, mas ele muda o estado físico do objeto de coleção (ele faz uma mudança muito real para o cache muito real), o método de thelookup deve ser
const
?A resposta é um sim retumbante. (Há exceções para cada regra, então “sim” deve realmente ter um asterisco ao lado dele,mas a grande maioria das vezes, a resposta é sim.)isto é tudo sobre “lógico
const
“sobre” físicoconst
.”Significa que a decisão sobre se decorar amethod comconst
deve depender principalmente se esse método deixa o estado lógico inalterado, independentemente(você está sentado?) (você pode querer sentar-se) independentemente de o método acontece para fazer mudanças muito reais para o estado físico muito real do objeto.No caso de que não afundar ou, no caso de você ainda não estão na dor, vamos provocá-lo separadamente em dois casos:
- Se um método de alterações em qualquer parte do objeto da lógica do estado, que logicamente é um modificador; ele não deve ser
const
evenif (como realmente acontece!) o método não altera nenhum pedaço físico do estado concreto do objeto.Por outro lado, um método é logicamente um inspetor e deve serconst
se ele nunca muda qualquer parte do estado slógico do objeto, mesmo se (como realmente acontece!) the method changes physical bits of the object’s concrete state.Se estiver confuso, leia novamente.se você não está confuso, mas está irritado, bom: você pode não gostar ainda, mas pelo menos você entende. Faça uma repetição de respiração profunda depois de mim: “a ness
const
de um método deve fazer sentido de fora do objeto.”se você ainda está com raiva, repita isso três vezes: “a constância de um método deve fazer sentido para os usuários do objeto, e esses usuários podem ver apenas o estado lógico do objeto.”
se você ainda está com raiva, desculpe, é o que é. Aguenta-te e vive com isso. Sim, haverá excepções.; todas as regras eram elas. Mas como regra, em geral, esta noção lógica
const
é boa para você e boa para o seu software.Mais uma coisa. Isto vai ficar iNano, mas vamos ser precisos sobre se um método muda o estado logical do objeto. Se você estiver fora da classe — você é um usuário normal, cada experiência que você poderia realizar (cada método ou Consequência de métodos que você chama) teria os mesmos resultados (mesmos valores de retorno, as mesmas exceções ou falta de exceções), independentemente de ter chamado pela primeira vez esse método de pesquisa. Se a função de pesquisa mudou qualquer comportamento futuro de qualquer método futuro (não apenas tornando-o mais rápido, mas mudou o resultado, mudou o valor de retorno, mudou a percepção), então o método de pesquisa mudou o estado lógico do objeto — é um mutuante. Mas se o método de pesquisa mudou nada além de talvez fazer algumas coisas mais rápido, então é um inspetor.What do I do if I want a const member function to make an “invisible” change to a data member?
utilizar
mutable
(ou, em último recurso, utilizarconst_cast
).uma pequena percentagem de inspetores precisa fazer mudanças no estado físico de um objeto que não podem ser observadas por externalusers — mudanças no estado físico, mas não lógico.
por exemplo, o objecto da colecção discutido anteriormente permitiu a sua última pesquisa na esperança de melhorar o desempenho da sua próxima pesquisa. Uma vez que o cache, neste exemplo, não pode ser observado diretamente por qualquer parte da interface pública do objeto collection (além de timing), sua existência e estado não é parte do estado lógico do objeto, então as mudanças são invisíveis para os usuários externos. O método de pesquisa é um inspetor, uma vez que nunca muda o estado lógico do objeto, independentemente do fato de que, pelo menos para a implementação atual, ele altera o estado físico do objeto.Quando os métodos alteram o estado físico mas não lógico, o método deve geralmente ser marcado como
const
, uma vez que é realmente um método de inspecção. Isso cria um problema: quando o compilador vê o seu métodoconst
mudando o estado físico do objetothis
, ele se queixará-ele dará ao seu código uma mensagem de erro.a linguagem de compilador C++ usa a palavra-chave
mutable
para ajudá-lo a abraçar esta noção lógicaconst
. Neste caso, você marcaria o cache com a palavra-chavemutable
, dessa forma o compilador sabe que é permitido mudar dentro de um métodoconst
ou através de qualquer outroconst
ponteiro ou referência. Na nossa linguagem, a palavra-chavemutable
marca as porções do estado físico do objecto que não fazem parte do estado lógico.a palavra-chave
mutable
vai imediatamente antes da declaração do membro de dados, ou seja, o mesmo lugar onde você poderia colocarconst
. A outra abordagem, não preferido, é lançar fora aconst
‘ness dothis
ponteiro, provavelmente através deconst_cast
palavras-chave:Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!
Após essa linha,
self
vai ter o mesmo bits comothis
, isto é,self == this
, masself
é umSet*
em vez de umconst Set*
(tecnicamentethis
é umconst Set* const
, mas o mais à direitaconst
é irrelevante para esta discussão).Isso significa que você pode usarself
para modificar o objeto apontado porthis
.Nota: existe um erro extremamente improvável que pode ocorrer comconst_cast
. Só acontece quando três rarefeitos são combinados ao mesmo tempo.: um membro de dados que deve sermutable
(como discutido acima), um compilerthat não oferece suporte amutable
palavras-chave e/ou um programador que não usá-lo, e um objeto que foi originallydefined serconst
(em oposição a uma normal, nãoconst
objeto que é apontado por um ponteiro-para-const
).Embora esta combinação seja tão rara que pode nunca acontecer com você, se alguma vez acontecer, o código pode não funcionar (theStandard diz que o comportamento é indefinido).Se alguma vez quiser utilizarconst_cast
, utilizemutable
. Em outras palavras, se você alguma vez precisar mudar um membro de um objeto, e esse objeto é apontado por um ponteiro-para-const
, a coisa mais segura e mais simples a fazer é adicionarmutable
à declaração do membro. Você pode usarconst_cast
se você tem certeza de que o objeto real não éconst
(por exemplo, se estiver certeza de que o objeto é declarado algo parecido com isto:Set
s;
), mas se o próprio objeto pode serconst
(por exemplo, se pode ser declarado como:const Set s;
), usemutable
em vez deconst_cast
.por favor, não escreva dizendo que a versão X do compilador Y na máquina Z permite alterar um não-
mutable
membro de um objectoconst
. Eu não me importo — é ilegal de acordo com a linguagem e seu código provavelmente vai falhar em um compilador diferente ou mesmo uma versão diferente (uma atualização) do mesmo compilador. Diz que não. Utilizarmutable
em vez disso. Escrever codethat é garantido para funcionar, não um código que não parece quebrar.Const_cast significa oportunidades de otimização perdidas?em teoria, sim; na prática, não.Mesmo que a linguagem proscrevesse
const_cast
, a única maneira de evitar a descarga do cache de registo através de uma chamada de função dos Membros seria resolver o problema do aliasing (i.e., para provar que não há não-const
ponteiros que apontam para o objeto). Isto só pode acontecer em raros casos (quando o objeto é construído no âmbito doconst
memberfunction invocação, e quando os não-const
função de membro invocações entre o objeto e a construçãoconst
função de membro invocação de são estaticamente ligado, e quando cada uma destas invocações, também éinline
d, andwhen o construtor de si éinline
d, e quando todas as funções de membro do construtor chama sãoinline
).por que o compilador me permite mudar um int depois de eu ter apontado para ele com um const int*?
porque “
const int* p
“significa”p
promete não mudar o*p
, “não”*p
promete não mudar.”fazendo com que um
const int*
aponte para umint
nãoconst
-ify oint
. Oint
não pode ser alterado através deconst int*
, mas se alguém tiver umint*
(nota: nãoconst
) que aponta a (“aliases”), o mesmoint
, entãoint*
pode ser usado para alterar oint
. 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) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}
Note que
main()
ef(const int*,int*)
podem estar em diferentes unidades de compilação compiladas em diferentes dias da semana. Nesse caso, não há nenhuma maneira do compilador pode possivelmente detectar a aliasing no tempo de compilação. Por conseguinte, não é possível estabelecer uma regra linguística que proíba este tipo de coisas. Na verdade, nós nem queremos fazer tal arule, já que em geral é considerado um recurso que você pode ter muitos ponteiros apontando para a mesma coisa. O fato de que um desses ponteiros promete não mudar a “coisa” subjacente é apenas uma promessa feita pelo ponteiro; não é uma promessa feita pela “coisa”.”Const Fred * p” significa que * p não pode mudar?Não! (This is related to the FAQ about aliasing ofint
pointers.)“
const Fred* p
” significa que oFred
não pode ser alterado através do ponteirop
, mas pode haver outras maneiras de obter em theobject sem passar através de umconst
(como um alias nãoconst
ponteiro como umFred*
). Por exemplo, se você tem dois ponteiros “const Fred* p
“e”Fred* q
” que apontam para o mesmo objetoFred
(aliasing), pointerq
pode ser usado para alterar o objetoFred
mas pointerp
não pode.class Fred {public: void inspect() const; // A const member function void mutate(); // A non-const member function};int main(){ Fred f; const Fred* p = &f; Fred* q = &f; p->inspect(); // Okay: No change to *p p->mutate(); // Error: Can't change *p via p q->inspect(); // Okay: q is allowed to inspect the object q->mutate(); // Okay: q is allowed to mutate the object f.inspect(); // Okay: f is allowed to inspect the object f.mutate(); // Okay: f is allowed to mutate the object // ...}
Why am I getting an error converting a Foo * * → const Foo**?
porque converter
Foo**
→const Foo**
seria inválido e perigoso.C++ permite a conversão (segura)
Foo*
→Foo const*
, mas dá um erro se você tentar implicitamente converterFoo**
→const Foo**
.a razão pela qual esse erro é uma coisa boa é dada abaixo. Mas primeiro, aqui está a solução mais comum: simplychange
const Foo**
aconst Foo* const*
:class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}
A razão, a conversão do
Foo**
→const Foo**
é perigoso é que ele iria deixá-lo silenciosamente e accidentallymodify umconst Foo
objeto sem um elenco:class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}
Se a
q = &p
linha eram legais,q
seria apontando emp
. A linha seguinte,*q = &x
, mudap
em si (desde*q
ép
) para apontarx
. Isso seria uma coisa ruim, uma vez que teríamos perdido o qualificadorconst
:p
é umFoo*
masx
é umconst Foo
. A linhap->modify()
explora a capacidade de modificar seu referent, que é o problema real, uma vez que acabamos modificando umconst Foo
.Por analogia, se você esconder um criminoso sob um disfarce legal, ele pode então explorar a confiança dada a esse disfarce.Isso é mau.felizmente, o C++ impede-o de fazer isto: a linha
q = &p
é assinalada pelo compilador C++ como um erro de compilação. Chamada de Atenção: Por favor, não indique o seu modo de contornar essa mensagem de erro de tempo de compilação. Diz Que Não!(Nota: existe uma semelhança conceptual entre esta e a proibição de converter
Derived**
paraBase**
.)
- Se um método de alterações em qualquer parte do objeto da lógica do estado, que logicamente é um modificador; ele não deve ser