PROJETOS E DECLARAÇÕES
Item 19: Trate o projeto de classe como projeto de tipo
Em C++, como em outras linguagens de programação orientada a objetos, a definição de uma nova classe define um novo tipo. Muito de seu tempo como desenvolvedor C++ será gasto melhorando o sistema de tipos. Isso significa que você não é apenas um projetista de classes, é um projetista de tipos. Sobrecarregar funções e operadores, controlar alocação e liberação de memória, definir a inicialização e a finalização de objetos – está tudo em suas mãos. Logo, você deve abordar o projeto de classes com o mesmo cuidado que os projetistas de linguagens dão aos tipos predefinidos nas linguagens. Projetar classes boas é um desafio porque projetar tipos bons é um desafio. Bons tipos possuem uma sintaxe natural, uma semântica intuitiva e uma ou mais implementações eficientes. Em C++, uma definição de classe planeja- da de forma deficiente pode fazer com que fique impossível alcançar qual- quer um desses objetivos. Mesmo as características de desempenho das funções membro de uma classe podem ser afetadas devido à forma como as funções são declaradas.
Como, então, você projeta classes eficazes? Primeiro, você deve entender as questões que está enfrentando. Praticamente todas as classes exigem que você se confronte com as questões a seguir, em que suas respostas muitas vezes levam a restrições em seu projeto:
• Como os objetos de seu novo tipo devem ser criados e destruí- dos? A forma como isso é feito influencia o projeto dos construtores e destrutores de sua classe, bem como suas funções de alocação e de liberação (operator new, operator new[], operator delete e operator delete[] – veja o Capítulo 8), se você as escrever.
• Como a inicialização de objetos difere da atribuição de objetos? A resposta para essa questão determina o comportamento e as diferen- ças entre seus construtores e os operadores de atribuição. É impor- tante não confundir a inicialização com a atribuição, porque essas operações correspondem a chamadas de diferentes funções (veja o Item 4).
• O que significa os objetos de seu novo tipo serem passados por valor? Lembre-se, o construtor de cópia define como a passagem por valor é implementada para um tipo.
• Quais são as restrições nos valores legais para o seu novo tipo? Normalmente, apenas algumas combinações de valores para os membros de dados de uma classe são válidas. Essas combinações determinam as invariantes que suas classes precisarão manter. As in- variantes determinam a verificação de erros que você precisará fazer dentro de suas funções membro, especialmente seus construtores, operadores de atribuição e funções de escrita (setters). Elas também podem afetar as exceções que suas funções lançam e, caso você as utilize, suas especificações de exceções das funções.
• Seu novo tipo se encaixa em um grafo de herança? Se você her- da de classes existentes, estará restrito ao projeto dessas classes, especialmente pelo fato de as funções serem virtuais ou não virtuais (veja os Itens 34 e 36). Se quiser permitir que outras classes her- dem de sua classe, isso determinará se as funções que você decla- rar serão virtuais, especialmente seu destrutor (veja o Item 7). • Que conversões de tipo são permitidas para seu novo tipo? Seu
tipo existe em um mar de outros tipos, então será que deveria exis- tir conversões entre seu tipo e outros tipos? Se você quiser permi- tir que objetos do tipo T1 sejam implicitamente convertidos em objetos do tipo T2, vai querer escrever uma função de conversão de tipos na classe T1 (como operator T2) ou um construtor não explícito na classe T2 que possa ser chamado com um só argu- mento. Se quiser permitir apenas conversões explícitas, vai querer escrever funções para realizar as conversões, mas você precisa fa- zer com que elas não sejam operadores de conversão de tipos ou construtores não explícitos que possam ser chamados com um ar- gumento. (Para ver um exemplo de funções de conversão implícitas e explícitas, veja o Item 15.)
• Quais operações e funções fazem sentido para o novo tipo? A res- posta para essa questão determina quais funções você vai declarar para a sua classe. Algumas funções serão funções membro, mas ou- tras não (veja os Itens 23, 24 e 46).
• Que funções padrão devem ser desabilitadas? Aquelas que você precisará declarar como privadas (veja o Item 6).
• Quem deve ter acesso aos membros de seu novo tipo? Essa ques- tão ajuda a determinar quais membros são públicos, quais são pro- tegidos e quais são privados. Ela também ajuda a determinar quais classes e/ou funções devem ser amigas, bem como se faz sentido ani- nhar uma classe dentro da outra.
• Qual é a “interface não declarada” de seu novo tipo? Que tipo de garantias de desempenho ela oferece à segurança das exceções (veja o Item 29) e ao uso de recursos (por exemplo, cadeados e memória dinâmica)? As garantias que você oferece nessas áreas vão impor res- trições na implementação de sua classe.
• Seu novo tipo é geral? Até que ponto? Talvez você não esteja real- mente definindo um novo tipo; talvez esteja definindo uma família completa de tipos. Se for esse o caso, você não quer definir uma nova classe, quer definir um template de classe.
• Um novo tipo é realmente o que você precisa? Se você está definin- do uma nova classe derivada apenas para que possa adicionar funcio- nalidades a uma classe existente, talvez possa atingir seus objetivos de