Quem pode mexer nos seus dados
Quando você escreveu sua primeira classe, provavelmente expôs todos os membros ao mundo externo. Isso funciona, mas joga fora um dos principais motivos pelos quais as classes existem: o encapsulamento - ocultar o estado interno de uma classe para que o resto do programa só possa interagir com ela por meio de uma superfície controlada. Os especificadores de acesso são como você traça essa fronteira.
Há exatamente três: public, private e protected. Cada um rotula os membros que vêm depois dele, e o rótulo decide qual código pode lê-los ou escrevê-los. Acerte isso e sua classe impõe as próprias regras; erre e qualquer bug em qualquer lugar pode corromper o estado do seu objeto.
Os três especificadores
Um especificador é uma palavra-chave seguida de dois pontos. Todo membro declarado depois dele - até o próximo especificador - fica sob aquele nível de acesso.
Descomente a última linha e o compilador se recusa a construir: balance é private, então main não pode mexer nele diretamente. É esse o objetivo - a única forma de alterar o saldo é por meio de deposit, o que significa que você pode depois adicionar validação (sem depósitos negativos, registro, limites) em um único lugar e confiar que ela é sempre aplicada.
Aqui está o detalhamento completo:
// acessível de...
// public qualquer lugar (qualquer código que tenha o objeto)
// private só os membros da própria classe (+ friends)
// protected os membros da própria classe E as classes derivadas (+ friends)
class versus struct: o padrão
Você pode escrever quantos blocos de especificadores quiser, em qualquer ordem. O que os membros recebem antes de você escrever o primeiro depende de você ter usado class ou struct:
- Em uma
class, os membros sãoprivatepor padrão. - Em um
struct, os membros sãopublicpor padrão.
Esse padrão é a única diferença em nível de linguagem entre as duas palavras-chave. Um struct pode ter métodos, construtores e seções private exatamente como uma class.
A convenção é usar struct para meros agrupamentos de dados públicos e class quando você quer comportamento e estado oculto - mas o compilador não impõe isso, só o padrão difere.
Encapsulamento com getters e setters
O padrão do dia a dia é: os dados vão para uma seção private e um método public dá acesso controlado. Um getter somente leitura retorna o valor; um setter valida antes de atribuir. É aqui que private compensa.
Como celsius é private, não há como infiltrar um valor inválido - toda escrita precisa passar por setCelsius, que protege a invariante. Repare que os getters estão marcados como const: eles prometem não modificar o objeto, então você também pode chamá-los em objetos const Temperature.
protected e a herança
protected só importa quando a herança entra em cena. Ele se comporta como private para o código externo, mas uma classe derivada pode acessá-lo. Use-o para membros que uma subclasse legitimamente precisa, mas que o público ainda não deveria ter.
Um erro comum de iniciante é recorrer a protected em todo membro de dados "por via das dúvidas, caso uma subclasse precise". Isso amplia silenciosamente o contrato da sua classe - agora cada subclasse pode depender daquele campo e você não pode alterá-lo livremente. Prefira private e só promova para protected quando uma classe derivada genuinamente precisar do acesso.
A saída de emergência friend
Às vezes uma única função ou classe externa legitimamente precisa enxergar suas entranhas - um caso clássico é um operador como << que você não pode tornar membro. A palavra-chave friend concede a essa única entidade nomeada acesso aos seus membros private e protected, e a nada mais.
friend é uma exceção deliberada e cirúrgica - a própria classe nomeia exatamente em quem confia, de modo que ninguém pode se conceder acesso de fora. Use-o com parcimônia; se você se pega adicionando muitos friends, provavelmente seus membros não deveriam ter sido private para começar, ou seu projeto precisa ser repensado.
Erros comuns a evitar
- Tornar todos os membros
public. Parece fácil, mas você perde toda a validação e as invariantes que o encapsulamento traz. Por padrão, dadosprivatecom métodospublic. - Esquecer o padrão de
class.class Foo { int x; };tornaxprivate, entãofoo.x = 5não vai compilar. Se você queria um simples agrupamento de dados, usestructou adicione um rótulopublic:. - Abusar de
protected. É uma fronteira mais fraca queprivatee só é relevante com herança. Recorrer a ele em todo lugar acopla as subclasses a campos que você talvez queira mudar. - Esperar que
privateseja um recurso de segurança. É uma regra de tempo de compilação que evita acesso acidental, não criptografia. Os bytes continuam na memória;privateé sobre design limpo, não sobre sigilo.
Próximo: Structs
Você já viu que um struct é, na verdade, apenas uma class cujos membros são public por padrão. A próxima página, structs, aprofunda quando esse padrão público-por-padrão é exatamente o que você quer - agregados leves para agrupar valores relacionados - e como o struct é usado no C++ idiomático ao lado de classes completas.
Perguntas frequentes
Qual é a diferença entre public, private e protected em C++?
Membros public são acessíveis de qualquer lugar. Membros private só são acessíveis de dentro da própria classe (e de seus friends). protected é como private, mas também permite que classes derivadas acessem o membro. Por convenção, você mantém os dados private e expõe o comportamento por meio de métodos public.
Os membros são privados por padrão em C++?
Em uma class, sim - tudo é private até você escrever um especificador de acesso. Em um struct, o padrão é public. Esse único padrão é a verdadeira diferença entre class e struct em C++; ambos podem ter métodos, construtores e especificadores de acesso.
O que a palavra-chave friend faz em C++?
friend concede a uma função ou classe específica acesso aos seus membros private e protected. É uma exceção deliberada e restrita ao encapsulamento - a classe nomeia exatamente em quem confia, de modo que o acesso nunca é concedido implicitamente.