O Incrível Constante de Referência Que não é Const
Enquanto estiver a trabalhar no NamedType biblioteca, eu me deparei com uma situação que me deixou atordoado em perplexidade: uma constante de referência que permite a modificação do objeto a que ela se refere. Sem const_cast
. Sem mutable
. Sem nada na manga.Como pode ser? E como fazer cumprir o const
nessa referência const?
a const reference that isn’t const
here is a description of a situation where this strange reference comes up. Considere a seguinte classe de wrapper:
template<typename T>class Wrapper{public: Wrapper(T const& value) : value_(value) {} T const& get() const { return value_; } private: T value_;};
Este Wrapper
classe armazena um valor do tipo T
, e dá acesso a ele através de get()
método (independentemente de que, se você está se perguntando se “ficar” é um bom nome, você pode querer ler esta discussão detalhada). A parte importante é que o método get()
só dá acesso apenas para leitura ao valor armazenado, porque ele retorna uma referência const.
para verificar isso, vamos instanciar o modelo com int
, por exemplo. Tentar modificar o valor armazenado através do método get()
não consegue compilar (tente carregar no botão Executar abaixo).
o que é muito bom, porque queremos que get()
seja apenas para leitura.
mas vamos agora instanciar Wrapper
com int&
. Um caso de uso para isso poderia ser para evitar uma cópia, ou manter o controle de possíveis modificações de a
, por exemplo. Agora vamos tentar modificar o valor armazenado através da referência const devolvida por get()
:
e se você acertar o RUN buttong você verá que … ele compila! E imprime o valor modificado através da referência da const, como se nada de especial tivesse acontecido.Não é desconcertante? Não sente a sensação tranquilizadora de segurança proporcionada pelo colapso de const
à nossa volta? Em quem podemos confiar?Vamos tentar entender o que está acontecendo com esta referência.
Const referência ou referência a const?
o cerne da questão é que a nossa referência a é uma referência const, mas não uma referência a const.
para entender o que isso significa, vamos decompor passo a passo o que está acontecendo na instanciação do modelo.
o método get()
devolve um const T&
, com T
vindo de template T
. No nosso segundo caso, T
é int&
, por isso const T&
é const (int&) &
.
vamos ver mais de perto este const (int&)
. int&
é uma referência que se refere a um int
, e é permitido modificar esse int
. Mas const (int&)
é uma referência int&
que é const
, o que significa que a própria referência não pode ser modificada.
o que significa modificar uma referência, para começar? Em teoria, significaria fazê-lo se referir a outro objeto, ou não se referir a nada. Mas ambas as possibilidades são ilegais em C++: uma referência não pode reequilibrar. Refere-se ao mesmo objeto durante todo o seu curso de vida.
assim sendo const
não diz muito para uma referência, uma vez que eles sempre são const
, uma vez que eles não podem reequilibrar. Isto implica que const (int&)
é efetivamente o mesmo tipo que int&
.
o que nos deixa com get()
retornando (int&) &
. E pelas regras das referências colapsando, este é int&
.O
So get
devolve um int&
. Sua interface diz T const&
, mas na verdade é int&
. Não é um código expressivo, pois não?
How to make the const reference a reference to const
Now that we’re past the first step of understanding the issue, how can we fix it? Ou seja, como podemos fazer get()
retornar uma referência ao valor armazenado que não permite modificá-lo?
uma maneira de fazer isso é inserir explicitamente um const
dentro da referência. Podemos fazer isso removendo a referência, adicionando um const
, e colocando a referência de volta. Para retirar a referência, nós usamos std::remove_reference
em C++11, ou a mais conveniente std::remove_reference_t
em C++14:
Como você pode ver, se você clicar em Executar, o esperado erro de compilação agora é exibida ao tentar modificar o valor armazenado em Wrapper
.
eu acho que é útil entender o que está acontecendo com esta referência, porque este é um código que parece ser seguro, mas realmente não é, e este é o tipo mais perigoso.
você pode brincar com o código nos playgrounds acima embutidos na página. E um grande agradecimento ao usuário da stack overflow rakete1111 por me ajudar a entender as razões por trás deste problema.
pode também gostar de
- a parte mais irritante: como detectá-la e corrigi-la rapidamente