A palavra-chave 'mutable' tem outra finalidade além de permitir que a variável seja modificada por uma função const?

527

Há algum tempo, deparei-me com um código que marcava uma variável de membro de uma classe com a mutablepalavra - chave. Tanto quanto posso ver, simplesmente permite modificar uma variável em um constmétodo:

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

É o único uso dessa palavra-chave ou existe mais do que aparenta? Desde então, usei essa técnica em uma classe, marcando uma boost::mutexfunção mutável como permitida constpara bloqueá-la por motivos de segurança de encadeamento, mas, para ser sincero, parece um hack.

Roubar
fonte
2
Uma pergunta, porém, se você não está modificando nada, por que você precisa usar um mutex em primeiro lugar? Eu só quero entender isso.
Misgevolution
@ Misgevolution você está modificando algo, você só está controlando quem / como pode fazer a modificação via const. Um exemplo realmente ingênuo, imagine se eu der apenas identificadores não-const aos amigos, os inimigos tiverem um identificador const. Amigos podem modificar, inimigos não.
Iheanyi # 22/17
1
Nota: aqui está um ótimo exemplo de uso da palavra mutable- chave : stackoverflow.com/questions/15999123/…
Gabriel Staples
Eu gostaria que pudesse ser usado para substituir const(de tipos), para que eu não precise fazer isso class A_mutable{}; using A = A_mutable const; mutable_t<A> a;:, se eu quiser const-por-default, ou seja mutable A a;(mutável explícito) e A a;(const implícita).
alfC 18/10/19

Respostas:

351

Permite a diferenciação de const bit a bit e const lógico. Const lógico é quando um objeto não muda de uma maneira que seja visível através da interface pública, como no seu exemplo de bloqueio. Outro exemplo seria uma classe que calcula um valor na primeira vez em que é solicitado e armazena em cache o resultado.

Como o c ++ 11 mutablepode ser usado em um lambda para indicar que as coisas capturadas por valor são modificáveis ​​(elas não são por padrão):

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda
KeithB
fonte
52
'mutable' não afeta a constância lógica / bit a bit. C ++ é apenas const bit a bit e a palavra-chave 'mutable' pode ser usada para excluir membros dessa verificação. Não é possível obter uma const 'lógica' em C ++ que não seja através de abstrações (por exemplo, SmartPtrs).
Richard Corden
111
@ Richard: você está perdendo o ponto. Não existe uma palavra-chave "const lógica"; é verdade, é uma diferenciação conceitual que o programador faz para decidir quais membros devem ser excluídos ao serem mutáveis, com base no entendimento do que constitui o estado observável lógico do objeto.
Tony Delroy
6
@ajay Sim, esse é o objetivo de marcar uma variável de membro como mutável, para permitir que ela seja alterada em objetos const.
KeithB
6
Por que alguém precisa ser mutável em lambdas? Não seria suficiente capturar variável por referência?
Giorgio
11
@ Giorgio: A diferença é que o modificado xdentro do lambda permanece dentro do lambda, ou seja, a função lambda só pode modificar sua própria cópia x. A alteração não é visível do lado de fora, o original xainda permanece inalterado. Considere que lambdas são implementadas como classes de functor; variáveis ​​capturadas correspondem a variáveis ​​membro.
Sebastian Mach
138

A mutablepalavra-chave é uma maneira de perfurar o constvéu que você coloca sobre seus objetos. Se você tiver uma referência const ou ponteiro para um objeto, não poderá modificar esse objeto de nenhuma maneira, exceto quando e como está marcadomutable .

Com sua constreferência ou ponteiro, você está limitado a:

  • acesso de leitura apenas para quaisquer membros de dados visíveis
  • permissão para chamar apenas métodos marcados como const.

A mutableexceção permite que você agora possa gravar ou definir membros de dados marcados mutable. Essa é a única diferença visível externamente.

Internamente, os constmétodos visíveis para você também podem gravar nos membros de dados marcados mutable. Essencialmente, o véu const é perfurado de forma abrangente. Cabe ao projetista da API garantir que mutableisso não destrua o constconceito e seja usado apenas em casos especiais úteis. A mutablepalavra-chave ajuda porque marca claramente os membros de dados que estão sujeitos a esses casos especiais.

Na prática, você pode usar constobsessivamente em toda a sua base de código (você deseja essencialmente "infectar" sua base de código com a const"doença"). Neste mundo, ponteiros e referências sãoconst muito poucas exceções, produzindo código mais fácil de raciocinar e entender. Para uma digressão interessante, procure "transparência referencial".

Sem a mutablepalavra-chave, você será forçado a usar const_castpara lidar com os vários casos especiais úteis que ela permite (armazenamento em cache, contagem de referências, dados de depuração etc.). Infelizmente, const_casté significativamente mais destrutivo do que mutableporque força o cliente da API a destruir a constproteção dos objetos que ele está usando. Além disso, causa constdestruição generalizada : o uso de const_castum ponteiro ou referência constante permite acesso irrestrito à gravação e à chamada de método a membros visíveis. Por outro lado, mutableexige que o designer da API exerça controle refinado sobre as constexceções e, geralmente, essas exceções estão ocultas nos constmétodos que operam com dados particulares.

(NB refiro-me à visibilidade de dados e métodos algumas vezes. Estou falando de membros marcados como públicos versus privados ou protegidos, que é um tipo totalmente diferente de proteção a objetos discutido aqui .)

Dan L
fonte
8
Além disso, usar const_castpara modificar uma parte de um constobjeto gera um comportamento indefinido.
Brian
Não concordo porque força o cliente da API a destruir a proteção constante dos objetos . Se você estivesse usando const_castpara implementar a mutação de variáveis-membro em um constmétodo, não pediria ao cliente para fazer a conversão - você faria isso dentro do método usando const_casting this. Basicamente, permite ignorar a constância em membros arbitrários em um site de chamada específico , enquanto mutablevamos remover a const em um membro específico em todos os sites de chamada. O último é geralmente o que você deseja para o uso típico (armazenamento em cache, estatísticas), mas às vezes o const_cast se encaixa no padrão.
BeeOnRope 18/06
1
O const_castpadrão se encaixa melhor em alguns casos, como quando você deseja modificar temporariamente um membro e depois restaurá-lo (praticamente como boost::mutex). O método é logicamente constante, pois o estado final é o mesmo que o inicial, mas você deseja fazer essa alteração transitória. const_castpode ser útil lá, porque permite que você expulte const especificamente nesse método, caso a mutação seja desfeita, mas mutablenão seria tão apropriada, pois removeria a proteção const de todos os métodos, que nem sempre seguem o "do , desfazer "padrão.
BeeOnRope
2
A possível colocação do objeto definido const na memória somente leitura (mais geralmente, a memória marcada como somente leitura) e a linguagem padrão associada que permite isso tornam const_castpossível uma bomba-relógio. mutablenão tem esse problema, pois esses objetos não podem ser colocados na memória somente leitura.
BeeOnRope 18/06
75

Seu uso com o boost :: mutex é exatamente o objetivo dessa palavra-chave. Outro uso é o cache interno de resultados para acelerar o acesso.

Basicamente, 'mutável' se aplica a qualquer atributo de classe que não afeta o estado visível externamente do objeto.

No código de exemplo da sua pergunta, mutable pode ser inadequado se o valor de done_ afetar o estado externo, depende do que está no ...; parte.

Frank Szczerba
fonte
35

Mutável é para marcar atributos específicos como modificáveis ​​a partir de constmétodos. Esse é o seu único objetivo. Pense com cuidado antes de usá-lo, porque seu código provavelmente será mais limpo e legível se você alterar o design em vez de usá-lo mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

Então, se a loucura acima não é para que serve o mutável, para que serve? Aqui está o caso sutil: mutável é para o caso em que um objeto é logicamente constante, mas na prática precisa mudar. Esses casos são poucos e distantes, mas existem.

Exemplos que o autor fornece incluem variáveis ​​de cache e depuração temporária.

John Millikin
fonte
2
Eu acho que esse link fornece o melhor exemplo de cenário em que o mutable é útil. Quase parece que eles são usados ​​exclusivamente para depuração. (por uso correto)
entusiasticgeek
O uso de mutablepode tornar o código mais legível e limpo. No exemplo a seguir, readpode ser consto esperado. `m_mutex mutável; Contêiner m_container; void add (item do item) {Lockguard lock (m_mutex); m_container.pushback (item); } Item read () const {Lockguard lock (m_mutex); retornar m_container.first (); } `
Th. Thielemann
Há um caso de uso extremamente popular: ref conta.
Seva Alekseyev
33

É útil em situações em que você oculta um estado interno, como um cache. Por exemplo:

classe HashTable
{
...
público:
    const de pesquisa (chave de cadeia)
    {
        if (chave == lastKey)
            return lastValue;

        valor da string = lookupInternal (key);

        lastKey = chave;
        lastValue = valor;

        valor de retorno;
    }

privado:
    string mutável lastKey, lastValue;
};

E então você pode ter um const HashTableobjeto ainda usando seu lookup()método, que modifica o cache interno.

Adam Rosenfield
fonte
9

mutable existe à medida que você deduz que permite modificar dados em uma função constante.

A intenção é que você possa ter uma função que "não faça nada" para o estado interno do objeto e, assim, marque a função const, mas poderá realmente precisar modificar alguns dos objetos de maneira que não afetem seu correto funcionalidade.

A palavra-chave pode atuar como uma dica para o compilador - um compilador teórico pode colocar um objeto constante (como um global) na memória que foi marcada como somente leitura. A presença de mutabledicas de que isso não deve ser feito.

Aqui estão alguns motivos válidos para declarar e usar dados mutáveis:

  • Segurança da linha. Declarar a mutable boost::mutexé perfeitamente razoável.
  • Estatisticas. Contando o número de chamadas para uma função, considerando alguns ou todos os seus argumentos.
  • Memoização. Calculando uma resposta cara e armazenando-a para referência futura, em vez de recalculá-la novamente.
Lloyd
fonte
2
Boa resposta, exceto pelo comentário sobre o mutável ser uma "dica". Isso faz parecer que o membro mutável às vezes não será mutável se o compilador colocou o objeto na ROM. O comportamento do mutável está bem definido.
Richard Corden
2
Além de colocar um objeto const na memória somente leitura, o compilador também pode optar por otimizar as chamadas de execução const fora de um loop, por exemplo. Um contador de estatísticas mutáveis ​​em uma função const ainda permitirá essa otimização (e contará apenas uma chamada) em vez de impedir a otimização apenas para contar mais chamadas.
Hagen von Eitzen
@HagenvonEitzen - Tenho certeza de que está incorreto. Um compilador não pode elevar funções de um loop, a menos que possa provar que não há efeitos colaterais. Essa prova geralmente envolve realmente inspecionar a implementação da função (geralmente depois que ela é incorporada) e não confiar const(e essa inspeção terá êxito ou falhará, independentemente de constou mutable). Simplesmente declarar a função constnão é suficiente: uma constfunção é livre para ter efeitos colaterais, como modificar uma variável global ou algo passado para a função, portanto, não é uma garantia útil para essa prova.
BeeOnRope 18/06
Agora, alguns compiladores têm extensões especiais, como _attribute __ ((const)) e __attribute __ ((pure)), que têm esses efeitos , mas isso é tangencialmente relacionado à constpalavra - chave em C ++.
BeeOnRope
8

Bem, sim, é isso que faz. Eu o uso para membros modificados por métodos que não alteram logicamente o estado de uma classe - por exemplo, para acelerar as pesquisas implementando um cache:

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

Agora, você deve usá-lo com cuidado - os problemas de simultaneidade são uma grande preocupação, pois um chamador pode assumir que eles são seguros para threads, apenas usando constmétodos. E, é claro, modificar mutabledados não deve alterar o comportamento do objeto de maneira significativa, algo que poderia ser violado pelo exemplo que eu dei, por exemplo, se se esperava que as alterações gravadas no disco fossem imediatamente visíveis para o aplicativo .

Shog9
fonte
6

Mutable é usado quando você tem uma variável dentro da classe que é usada apenas nessa classe para sinalizar coisas como, por exemplo, um mutex ou um bloqueio. Essa variável não altera o comportamento da classe, mas é necessária para implementar a segurança do encadeamento da própria classe. Portanto, se sem "mutável", você não seria capaz de ter funções "const" porque essa variável precisará ser alterada em todas as funções disponíveis para o mundo externo. Portanto, mutable foi introduzido para tornar uma variável membro gravável mesmo por uma função const.

O mutable especificado informa ao compilador e ao leitor que é seguro e esperado que uma variável de membro possa ser modificada dentro de uma função de membro const.

mkschreder
fonte
4

mutable é usado principalmente em um detalhe de implementação da classe. O usuário da classe não precisa saber disso, portanto, o método que ele acha que "deveria" ser const pode ser. Seu exemplo de ter um mutex mutável é um bom exemplo canônico.

Greg Rogers
fonte
4

Seu uso não é um hack, embora, como muitas coisas em C ++, o mutable possa ser hack para um programador preguiçoso que não queira voltar e marcar algo que não deve ser const como não-const.

JohnMcG
fonte
3

Use "mutable" quando para coisas que são LOGICALLY sem estado para o usuário (e, portanto, devem ter getters "const" nas APIs da classe pública), mas NÃO são sem estado na IMPLEMENTATION subjacente (o código no seu .cpp).

Os casos que eu o uso com mais frequência são a inicialização lenta de membros sem estado "simples de dados antigos". Nomeadamente, é ideal nos casos limitados em que esses membros são caros para construir (processador) ou transportar (memória) e muitos usuários do objeto nunca os solicitarão. Nessa situação, você deseja uma construção lenta no back-end para obter desempenho, já que 90% dos objetos criados nunca precisarão compilá-los, mas você ainda precisará apresentar a API sem estado correta para consumo público.

Zack Yezek
fonte
2

Mutable altera o significado de constconst bit a bit para const lógico para a classe.

Isso significa que as classes com membros mutáveis ​​são mais constantes em bits e não aparecerão mais nas seções somente leitura do executável.

Além disso, modifica a verificação de tipo, permitindo que as constfunções de membro alterem membros mutáveis ​​sem usar const_cast.

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

Veja as outras respostas para obter mais detalhes, mas eu gostaria de destacar que não é apenas para segurança de tipo e que afeta o resultado compilado.

Kevin Cox
fonte
1

Em alguns casos (como iteradores mal projetados), a classe precisa manter uma contagem ou algum outro valor incidental, que realmente não afeta o "estado" principal da classe. É na maioria das vezes que vejo mutável usado. Sem mutável, você seria forçado a sacrificar toda a constância de seu design.

Parece um hack a maior parte do tempo para mim também. Útil em muito poucas situações.

Joe Schneider
fonte
1

O exemplo clássico (como mencionado em outras respostas) e a única situação em que vi a mutablepalavra - chave usada até agora, é para armazenar em cache o resultado de uma tarefa complicada.Get método , em que o cache é implementado como um membro de dados da classe e não como um variável estática no método (por razões de compartilhamento entre várias funções ou limpeza simples).

Em geral, as alternativas ao uso da mutablepalavra - chave geralmente são uma variável estática no método ou no const_casttruque.

Outra explicação detalhada está aqui .

Daniel Hershcovich
fonte
1
Nunca ouvi falar em usar membros estáticos como uma alternativa geral aos membros mutáveis. E const_casté somente quando você souber (ou tiver sido garantido) que algo não será alterado (por exemplo, ao interferir nas bibliotecas C) ou quando você souber que não foi declarado const. Ou seja, a modificação de uma variável constante constante resulta em um comportamento indefinido.
Sebastian Mach
1
@phresnel Por "variáveis ​​estáticas", quis dizer variáveis ​​automáticas estáticas no método (que permanecem entre as chamadas). E const_castpode ser usado para modificar um membro da classe em um constmétodo, que é o que me referi ...
Daniel Hershcovich
1
Isso não ficou claro para mim, como você escreveu "em geral" :) Com relação à modificação const_cast, como dito, isso só é permitido quando o objeto não foi declarado const. Por exemplo const Frob f; f.something();, com void something() const { const_cast<int&>(m_foo) = 2;resultados em comportamento indefinido.
Sebastian Mach
1

O mutável pode ser útil quando você está substituindo uma função virtual const e deseja modificar sua variável de membro da classe filho nessa função. Na maioria dos casos, você não deseja alterar a interface da classe base, portanto, você deve usar sua própria variável de membro mutável.

Saurabh
fonte
1

A palavra-chave mutável é muito útil ao criar stubs para fins de teste de classe. Você pode stub uma função const e ainda conseguir aumentar contadores (mutáveis) ou qualquer funcionalidade de teste que você adicionou ao seu stub. Isso mantém intacta a interface da classe stubbed.

Martin G
fonte
0

Um dos melhores exemplos em que usamos mutable é, em cópia profunda. no construtor copy, enviamos const &objcomo argumento. Portanto, o novo objeto criado será do tipo constante. Se queremos mudar (na maioria das vezes não mudamos, em casos raros, podemos mudar) os membros desse objeto const recém-criado, precisamos declará-lo como mutable.

mutableA classe de armazenamento pode ser usada apenas no membro de dados não estático e não const de uma classe. O membro de dados mutáveis ​​de uma classe pode ser modificado mesmo que faça parte de um objeto declarado como const.

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

No exemplo acima, somos capazes de alterar o valor da variável membro, xembora faça parte de um objeto declarado como const. Isso ocorre porque a variável xé declarada como mutável. Mas se você tentar modificar o valor da variável membro y, o compilador lançará um erro.

Venkatakrishna Kalepalli
fonte
-1

A própria palavra-chave 'mutável' é na verdade uma palavra-chave reservada. Em seguida, é usada para variar o valor da variável constante. Se você deseja ter vários valores de um constsnt, use a palavra-chave mutável.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
Rajdeep Rathore
fonte