• Nenhum resultado encontrado

C++ Eficaz - 55 Maneiras de Aprimorar Seus Programas e Projetos - 3ª Ed

N/A
N/A
Protected

Academic year: 2021

Share "C++ Eficaz - 55 Maneiras de Aprimorar Seus Programas e Projetos - 3ª Ed"

Copied!
304
0
0

Texto

(1)

LINGUAGENS DE PROGRAMAÇÃO

55 maneiras de aprimorar

seus programas e projetos

55 maneir

as de aprimor

ar

seus progr

amas e projetos

Scott Meyers

Scott Mey

ers

Scott

Meyers

Scott Meyers

Com uma abordagem prática,

C++ Efi caz

descreve os

princípios mais usados – e os quase sempre evitados –

pelos experts para produzir código claro, correto e efi caz.

Organizado em 55 regras específi cas, que examinam

métodos para melhor escrever C++ e apresentam inúmeros

exemplos concretos, este é um guia valioso para todos

os que desejam usar C++ de maneira segura e efi ciente,

ou que estejam fazendo a transição de outra linguagem

orientada a objeto.

Alguns dos recursos importantes de

C++ Efi caz

são:

Guia especializado de projeto efi caz de classes,

funções, templates e hierarquias de herança.

Aplicações da nova funcionalidade de biblioteca

padrão “TR1” acompanhadas de comparações a

componentes de outras bibliotecas padrão.

Dicas sobre as diferenças entre C++ e outras linguagens

(como Java, C#, C), que ajudam os desenvolvedores a

assimilar o “jeito C++” de fazer as coisas.

PROGRAMAÇÃO

Aguilar, L.

Fundamentos de Programação: Algoritmos, Estruturas

de Dados e Objetos, 3.ed.

Aguilar, L.

Programação em C++: Algoritmos, Estruturas de Dados

e Objetos

Arnold, Golsing e Holmes

A Linguagem de Programação Java, 4.ed.

Flanagan, D.

Java: O Guia Essencial, 5.ed.

Flanagan, D.

JavaScript: O Guia Defi nitivo, 4.ed.

Galuppo, Matheus e Santos

Desenvolvendo com C#

Horstmann, C.

Big Java

Horstmann, C.

Conceitos de Computação com o Essencial de C++, 3.ed.

Horstmann, C.

Conceitos de Computação com Java, 5.ed.

Hubbard, J.

Programação em C++, 2.ed. (Coleção Schaum)

Hubbard, J.

Programação com Java, 2.ed. (Coleção Schaum)

Hunt e Thomas

O Programador Pragmático

Lippman, S.

C#: Um Guia Prático

Sebesta, R.

Conceitos de Linguagens de Programação, 9.ed.

Stroustrup, B.

A Linguagem de Programação C++ , 3.ed.

*Stroustrup, B.

Princípios e Prática de Programação Usando C++

Tucker e Noonan

Linguagens de Programação: Princípios e Paradigmas

*Livro em produção no momento de impressão desta obra, mas que muito em breve estará disponível para os leitores em língua portuguesa.

55 maneir

as de aprimo

r

seus progr

amas e projetos

55 maneiras de aprimorar

seus programas e projetos

C

++

Eficaz

C

++

Ef

icaz

C

++

Ef

icaz

C

++

Eficaz

terceira edição

terceira edição

ter

ceir

a edição

ter

ceir

a edição

ra

93155 C ++ Eficaz NV.indd 1 3/2/2011 08:36:13

(2)

Catalogação na publicação: Ana Paula M. Magnus – CRB 10/2052 M612c Meyers, Scott.

C++ eficaz [recurso eletrônico] : 55 maneiras de aprimorar seus programas e projetos / Scott Meyers ; tradução técnica: Eduardo Kessler Piveta. – 3. ed. – Dados eletrônicos. – Porto Alegre : Bookman, 2011.

Editado também como livro impresso em 2011.

ISBN 978-85-7780-820-5

1. Computação – Linguagem de programação – C++.

I. Título.

(3)

Tradução técnica:

Eduardo Kessler Piveta

Doutor em Ciência da Computação – UFRGS

Professor Adjunto da Universidade Federal de Santa Maria – UFSM

2011 Versão impressa desta obra: 2011

(4)

Reservados todos os direitos de publicação, em língua portuguesa, à ARTMED® EDITORA S.A.

(BOOKMAN® COMPANHIA EDITORA é uma divisão da ARTMED® EDITORA S.A.)

Av. Jerônimo de Ornelas, 670 - Santana 90040-340 Porto Alegre RS

Fone (51) 3027-7000 Fax (51) 3027-7070

É proibida a duplicação ou reprodução deste volume, no todo ou em parte, sob quaisquer formas ou por quaisquer meios (eletrônico, mecânico, gravação, fotocópia, distribuição na Web e outros), sem permissão expressa da Editora.

SÃO PAULO

Av. Embaixador Macedo Soares, 10.735 - Pavilhão 5 - Cond. Espace Center Vila Anastácio 05095-035 São Paulo SP

Fone (11) 3665-1100 Fax (11) 3667-1333 SAC 0800 703-3444

IMPRESSO NO BRASIL

PRINTED IN BRAZIL

Obra originalmente publicada sob o título

Effective C++: 55 Specific Ways to Improve Your Programs and Designs, 3rd Edition. ISBN 0321334876 / 978-032-133487-9

Arte da capa de Michio Hoshino, Minden Pictures. Fotografia do autor de Timothy J. Park.

Capa: Rogério Grilho, arte sobre capa original Preparação de original: Daniel Grassi Leitura final: Taís Bopp da Silva

Editora Sênior – Bookman: Arysinha Jacques Affonso Editora responsável por esta obra: Elisa Etzberger Viali Projeto e editoração: Techbooks

Authorized translation from the English language edition, entitled EFFECTIVE C++: 55 SPECIFIC WAYS TO IMPROVE YOUR PROGRAMS AND DESIGNS, 3rd Edition, by MEYERS,SCOTT, published by Pearson Education,Inc., publishing as Addison-Wesley Professional, Copyright © 2005. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education,Inc.

Portuguese language edition published by Bookman Companhia Editora Ltda, a Division of Artmed Editora SA, Copyright © 2011

Tradução autorizada a partir do original em língua inglesa da obra intitulada EFFECTIVE C++: 55 SPECIFIC WAYS TO IMPROVE YOUR PROGRAMS AND DESIGNS, 3ªEdição, autoria de MEYERS,SCOTT, publicado por Pearson Education, Inc., sob o selo Addison-Wesley Professional, Copyright © 2005. Todos os direitos reservados. Este livro não poderá ser reproduzido nem em parte nem na íntegra, nem ter partes ou sua íntegra armazenado em qualquer meio, seja mecânico ou eletrônico, inclusive fotoreprografação, sem permissão da Pearson Education,Inc.

A edição em língua portuguesa desta obra é publicada por Bookman Companhia Editora Ltda, uma Divisão de Artmed Editora SA, Copyright © 2011

(5)

Scott Meyers é um dos mais importantes especialistas em desen-volvimento de software C++ do mundo. Autor de diversos livros sobre o tema, também é consultor e membro de conselho edi-torial de editoras e revistas e já foi membro de comitês técnicos consultivos de várias empresas iniciantes. Recebeu o título de Ph.D em ciência da computação pela Brown University, Rhode Island, Estados Unidos, em 1993. O endereço de seu site é www. aristeia.com.

(6)

Para Nancy: sem ela, nada valeria muito a pena ser feito. E em memória de Persephone, 1995–2004

(7)

C++ Eficaz existe há quinze anos*, e eu comecei a aprender C++ cerca de cinco anos antes de escrever este livro. O “projeto C++ Eficaz” tem estado em desenvolvimento por mais de duas décadas. Durante esse tempo, eu me beneficiei de ideias, sugestões, correções e, ocasionalmente, de conhecimen-to de centenas (milhares?) de pessoas. Cada uma delas ajudou a melhorar C++ Eficaz. Sou grato a todas elas.

Desisti de tentar acompanhar onde aprendi cada coisa, mas uma fonte geral de informação tem me ajudado desde o início: o grupo de notícias de C++ da Usenet, especialmente comp.lang.c++.moderated e comp.std.c++. Muitos dos Itens neste livro – talvez a maioria deles – se beneficiaram do fluxo de ideias técnicas nas quais os participantes desse grupo são especia-listas.

Em relação ao novo material da terceira edição, Steve Dewhurst trabalhou comi-go para chegarmos a um conjunto inicial de Itens candidatos. No Item 11, a ideia de implementar operator= por meio da técnica de copiar e trocar veio dos tex-tos de Herb Sutter sobre o tópico, ou seja, do Item 13 de seu Exceptional C++ (Addison-Wesley, 2000). RAII (veja o Item 13) é de Bjarne Stroustrup em The C++ Programming Language (Addison-Wesley, 2000). A ideia por trás do Item 17 veio da seção de “Melhores Práticas” da página Web de shared_ptr de Boost, http://boost.org/libs/smart_ptr/shared_ptr.htm#Best-Practices, e foi refinada pelo Item 21 do livro More Exceptional C++, de Herb Sutter (Addison-Wesley, 2002). O Item 29 foi muito influenciado pelos textos abran-gentes de Herb Sutter sobre o tópico, ou seja, os Itens 8-19 de Exceptional C++, os Itens 17–23 de More Exceptional C++ e os Itens 11–13 de Exceptio-nal C++ Style (Addison-Wesley, 2005); David Abrahams me ajudou a entender melhor as três garantias de segurança de exceções. O idioma NVI no Item 35 é da coluna, “Virtuality”, de Herb Sutter, de setembro de 2001, do C/C++ Users Journal. No mesmo item, os padrões de projeto Template, Método e Estratégia são do livro Padrões de Projeto (Bookman, 2000) de Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. A ideia de usar o idioma NVI no Item 37 é de Hendrik Schober. David Smallberg contribuiu com a motivação para escrever uma implementação personalizada de set no Item 38. A observação

* N. de T.: Em 2005, quando o original em inglês foi escrito.

(8)

x AGRADECIMENTOS

no Item 39, de que o EBO geralmente não está disponível sob herança múl-tipla, é de David Vandevoorde e Nicolai M. Josuttis no livro C++ Templates (Addison-Wesley, 2003). No Item 42, meu entendimento inicial sobre typename veio da FAQ sobre C++ e C de Greg Comeau (http://www.comeaucomputing. com/techtalk/#typename), e Leor Zolman me ajudou a entender que meu en-tendimento estava incorreto (falha minha, não de Greg). A essência do Item 46 é da apresentação de Dan Saks chamada “Making New Friends”. A ideia no final do Item 52, de que, se você declarar uma versão de operator new, você deve declarar todas elas, é do Item 22 do livro Exceptional C++ Style de Herb Sutter. Meu entendimento do processo de revisão de Boost (resumido no Item 55) foi refinado por David Abrahams.

Tudo acima corresponde a com quem ou onde eu aprendi algo, não neces-sariamente a quem inventou ou onde foi inventado ou publicado primeiro. Minhas notas me dizem que eu também usei informações de Steve Clamage, Antoine Trux, Timothy Knox e Mike Kaelbling, embora, infelizmente, elas não me digam como ou onde as usei.

Os rascunhos da primeira edição foram revisados por Tom Cargill, Glenn Carroll, Tony Davis, Brian Kernighan, Jak Kirman, Doug Lea, Moises Lejter, Eugene Santos Jr., John Shewchuk, John Stasko, Bjarne Stroustrup, Bar-bara Tilly e Nancy L. Urbano. Recebi sugestões de melhorias, que consegui incorporar em impressões posteriores, de Nancy L. Urbano, Chris Treichel, David Corbin, Paul Gibson, Steve Vinoski, Tom Cargill, Neil Rhodes, David Bern, Russ Williams, Robert Brazile, Doug Morgan, Uwe Steinmüller, Mark Somer, Doug Moore, David Smallberg, Seth Meltzer, Oleg Shteynbuk, Da-vid Papurt, Tony Hansen, Peter McCluskey, Stefan Kuhlins, DaDa-vid Braunegg, Paul Chisholm, Adam Zell, Clovis Tondo, Mike Kaelbling, Natraj Kini, Lars Nyman, Greg Lutz, Tim Johnson, John Lakos, Roger Scott, Scott Frohman, Alan Rooks, Robert Poor, Eric Nagler, Antoine Trux, Cade Roux, Chandrika Gokul, Randy Mangoba e Glenn Teitelbaum.

Os rascunhos da segunda edição foram revisados por Derek Bosch, Tim Johnson, Brian Kernighan, Junichi Kimura, Scott Lewandowski, Laura Michaels, David Smallberg, Clovis Tondo, Chris Van Wyk e Oleg Zabluda. As impressões posteriores aproveitaram os comentários de Daniel Stein-berg, Arunprasad Marathe, Doug Stapp, Robert Hall, Cheryl Ferguson, Gary Bartlett, Michael Tamm, Kendall Beaman, Eric Nagler, Max Hailperin, Joe Gottman, Richard Weeks, Valentin Bonnard, Jun He, Tim King, Don Maier, Ted Hill, Mark Harrison, Michael Rubenstein, Mark Rodgers, David Goh, Brenton Cooper, Andy Thomas-Cramer, Antoine Trux, John Wait, Brian Sharon, Liam Fitzpatrick, Bernd Mohr, Gary Yee, John O’Hanley, Brady Patterson, Christopher Peterson, Feliks Kluzniak, Isi Dunietz, Christopher Creutzi, Ian Cooper, Carl Harris, Mark Stickel, Clay Budin, Panayotis Matsi-nopoulos, David Smallberg, Herb Sutter, Pajo Misljencevic, Giulio Agostini, Fredrik Blomqvist, Jimmy Snyder, Byrial Jensen, Witold Kuzminski, Ka-zunobu Kuriyama, Michael Christensen, Jorge Yáñez Teruel, Mark Davis, Marty Rabinowitz, Ares Lagae e Alexander Medvedev.

(9)

O rascunho inicial e parcial desta edição foi revisado por Brian Kernighan, Angelika Langer, Jesse Laeuchli, Roger E. Pedersen, Chris Van Wyk, Nicho-las Stroustrup e Hendrik Schober. Os revisores do manuscrito completo foram Leor Zolman, Mike Tsao, Eric Nagler, Gene Gutnik, David Abrahams, Gerhard Kreuzer, Drosos Kourounis, Brian Kernighan, Andrew Kirmse, Ba-log Pal, Emily Jagdhar, Eugene Kalenkovich, Mike Roze, Enrico Carrara, Benjamin Berck, Jack Reeves, Steve Schirripa, Martin Fallenstedt, Timothy Knox, Yun Bai, Michael Lanzetta, Philipp Janert, Guido Bartolucci, Micha-el Topic, Jeff ScherpMicha-elz, Chris Nauroth, Nishant Mittal, Jeff Somers, Hal Moroff, Vincent Manis, Brandon Chang, Greg Li, Jim Meehan, Alan Geller, Siddhartha Singh, Sam Lee, Sasan Dashtinezhad, Alex Marin, Steve Cai, Thomas Fruchterman, Cory Hicks, David Smallberg, Gunavardhan Kaku-lapati, Danny Rabbani, Jake Cohen, Hendrik Schober, Paco Viciana, Glenn Kennedy, Jeffrey D. Oldham, Nicholas Stroustrup, Matthew Wilson, Andrei Alexandrescu, Tim Johnson, Leon Matthews, Peter Dulimov e Kevlin Hen-ney. Os rascunhos de alguns Itens individuais foram revisados por Herb Sutter e Attila F. Feher.

Revisar um manuscrito não lapidado (possivelmente incompleto) é um tra-balho que exige bastante, e fazê-lo sob a pressão do tempo apenas dificulta a tarefa. Continuo sendo grato ao fato de que tantas pessoas estavam dispos-tas a fazê-lo para mim.

Revisar é mais difícil ainda se você não tem experiência com o material que está sendo discutido e quando se espera que você capture todos os pro-blemas no manuscrito. Surpreende o fato de que algumas pessoas ainda escolham ser redatores/revisores. Chrysta Meadowbrooke foi a redatora/re-visora deste livro, e seu trabalho muito cuidadoso expôs muitos problemas que haviam passado batidos por todo mundo.

Leor Zolman verificou todos os exemplos de código em vários compiladores na preparação para a revisão completa, e então fez isso novamente após eu ter revisado o manuscrito. Se ainda houver erros lá, sou responsável por eles, não Leor.

Karl Wiegers e, especialmente, Tim Johnson ofereceram um feedback rápi-do e prestativo sobre a contracapa rápi-do livro na edição norte-americana. Desde a publicação da primeira edição, incorporei revisões sugeridas por Jason Ross, Robert Yokota, Bernhard Merkle, Attila Fehér, Gerhard Kreuzer, Marcin Sochacki, J. Daniel Smith, Idan Lupinsky, G. Wade Johnson, Clovis Tondo, Joshua Lehrer, T. David Hudson, Phillip Hellewell, Thomas Schell, Eldar Ronen, Ken Kobayashi, Cameron Mac Minn, John Hershberger, Alex Dumov, Vincent Stojanov, Andrew Henrick, Jiongxiong Chen, Balbir Singh, Fraser Ross, Niels Dekker, Harsh Gaurav Vangani, Vasily Poshehonov, Yuki-toshi Fujimura, Alex Howlett, Ed Ji Xihuang. Mike Rizzi, Balog Pal, David Solomon, Tony Oliver, Martin Rottinger, Miaohua, Brian Johnson, Joe Su-zow, Effeer Chen, Nate Kohl, Zachary Cohen, Owen Chu e Molly Sharp.

(10)

xii AGRADECIMENTOS

John Wait, meu editor das duas primeiras edições deste livro, tolamente se alistou mais uma vez nesse serviço. Sua assistente, Denise Mickelsen, li-dou rápida e eficientemente com minhas irritações, sempre com um sorriso agradável. (Ao menos eu acho que ela estava sorrindo. Na verdade, nunca a vi.) Julie Nahil juntou-se ao grupo e logo se tornou minha gerente de produ-ção. Com uma calma notável, ela lidou com a perda, do dia para a noite, de seis semanas no cronograma de produção. John Fuller (chefe dela) e Marty Rabinowitz (chefe dele) ajudaram com questões de produção também. O trabalho oficial de Vanessa Moore era ajudar nas questões do FrameMaker e na preparação dos arquivos PDF, mas ela também adicionou as entradas do Apêndice B e o formatou para a impressão na contracapa. Solveig Haugland me ajudou com a formatação do índice. Sandra Schroeder e Chuti Prasert-sith foram responsáveis pelo design da capa, embora coubesse a Chuti re-trabalhar a capa cada vez que eu dizia: “Mas e o que vocês acham desta foto com uma faixa daquela cor?”. Chanda Leary-Coutu foi escalada para o trabalho pesado de marketing.

Durante os meses em que trabalhei no manuscrito, muitas vezes a série de TV Buffy, a Caça-Vampiros me ajudou a “desestressar” no fim do dia. Foi com muito esforço que consegui manter as falas de Buffy fora do livro. Kathy Reed me ensinou a programar em 1971, e sou grato por continu-armos amigos até hoje. Donald French contratou a mim e a Moises Lejter para criar materiais de treinamento para C++ em 1989 (uma atitude que me levou a realmente conhecer C++), e em 1991 ele me estimulou a apre-sentá-los na Stratus Computer. Os alunos daquela turma me incentivaram a escrever o que se tornou a primeira edição deste livro. Don também me apresentou a John Wait, que concordou em publicá-lo.

Minha esposa, Nancy L. Urbano, continua me estimulando a escrever, mesmo após sete projetos de livros, uma adaptação em CD e uma dissertação. Ela tem uma paciência inacreditável. Eu não poderia fazer o que faço sem ela. Do início ao fim, nossa cadela, Persephone, foi uma companhia sem igual. Infelizmente, em boa parte deste projeto, sua companhia tomou a forma de uma urna funerária no escritório. Realmente sentimos a sua falta.

(11)

Satyricon, XCIV

(12)

Escrevi a edição original de C++ Eficaz em 1991. Quando chegou a hora de uma segunda edição, em 1997, atualizei o material em questões impor-tantes, mas, como não queria confundir os leitores familiarizados com a primeira edição, tentei ao máximo manter a estrutura existente: 48 dos 50 títulos de itens originais permaneceram essencialmente iguais. Se o livro fosse uma casa, a segunda edição seria o equivalente a trocar o carpete, fazer uma nova pintura e trocar algumas lâmpadas.

Para a terceira edição, derrubei tudo. (Houve momentos em que quis come-çar da fundação.) O mundo de C++ passou por enormes mudanças desde 1991, e o objetivo deste livro – identificar as recomendações de programa-ção C++ mais importantes em pacotes pequenos e legíveis – não mais era atendido pelos itens que estabeleci cerca de 15 anos antes. Em 1991, era razoável imaginar que os programadores de C++ vinham de uma experiên-cia com C. Hoje, os programadores que estão adotando C++ também vêm de Java ou de C#. Em 1991, a herança e a programação orientada a objetos eram novidade para a maioria dos programadores; hoje, são conceitos bem estabelecidos, e as exceções, os templates e a programação genérica são as áreas nas quais as pessoas precisam de mais ajuda. Em 1991, ninguém antes ouvira falar de padrões de projeto; hoje, é difícil discutir sistemas de software sem se referir a eles. Em 1991, o trabalho para a definição de um padrão para C++ estava recém começando; hoje, esse padrão já tem oito anos, e o trabalho para a próxima versão já começou.

Para lidar com essas mudanças, tentei começar o mais próximo possível do zero e me perguntei: “quais são os conselhos mais importantes para os programadores de C++ em 2005?”. O resultado é o conjunto de itens nesta nova edição. O livro tem novos capítulos sobre gerenciamento de recursos e sobre programação com templates. Na verdade, interesses relacionados aos templates estão mesclados ao longo do texto, porque afetam praticamente tudo em C++. O livro também inclui novo material sobre programação na presença de exceções, sobre a aplicação de padrões de projeto e sobre o uso dos novos recursos da biblioteca do TR1 (TR1 é descrito no Item 54). O livro reconhece que as técnicas e abordagens que funcionam bem em sis-temas com uma linha de execução apenas podem não ser apropriadas em sistemas com várias linhas de execução. Bem, mais ou menos metade do

(13)

material no livro é nova. Entretanto, a maioria das informações fundamen-tais da segunda edição continua sendo importante, então encontrei uma maneira de mantê-las de um jeito ou de outro.

Trabalhei duro para deixar este livro o melhor possível, mas não tenho ilu-sões que ele seja perfeito. Se você achar que alguns itens neste livro são inadequados como recomendação geral, que existe uma maneira melhor de realizar uma tarefa examinada no livro, ou que uma ou mais discussões técnicas não estão claras, estão incompletas, ou levam a entendimentos equivocados, por favor, me avise. Se você encontrar um erro de qualquer natureza – técnico, gramatical, tipográfico, qualquer que seja – por favor, me avise também. Adicionarei, com prazer, nos agradecimentos em impres-sões posteriores, o nome da primeira pessoa a chamar minha atenção para os problemas.

Mesmo com o número de itens tendo subido para 55, o conjunto de reco-mendações neste livro está longe de esgotar o tema. Mas chegar a regras boas – que servem para praticamente todas as aplicações o tempo todo – é mais difícil do que pode parecer. Se você tiver sugestões de novas recomen-dações, ficarei muito feliz de ouvir sobre elas.

Mantenho uma lista de mudanças deste livro desde sua primeira im-pressão, incluindo correções de erros, esclarecimentos e atualizações técnicas. A lista está disponível na página Effective C++ Errata, em http://aristeia.com/BookErrata/ec++3e-errata.html. Se você quiser ser notificado quando eu atualizá-la, cadastre-se em minha lista de e-mails. Eu a uso para fazer anúncios que possam interessar as pessoas que acompanham meu trabalho profissional. Para mais detalhes, consulte http://aristeia.com/MailingList/.

Scott Douglas Meyers http://aristeia.com/

Stafford, Oregon

Abril de

2005

(14)

SUMÁRIO

Introdução

21

Capítulo 1 Acostumando-se com a Linguagem C++

31

Item 1: Pense em C++ como um conjunto de linguagens 31 Item 2: Prefira constantes, enumerações e

internalizações a definições 33 Item 3: Use const sempre que possível 37 Item 4: Certifique-se de que os objetos sejam inicializados

antes do uso 46

Capítulo 2 Construtores, destrutores e operadores de atribuição

54

Item 5: Saiba quais funções C++ escreve e chama

silenciosamente 54 Item 6: Desabilite explicitamente o uso de funções

geradas pelo compilador que você não queira 57 Item 7: Declare os construtores como virtuais em

classes-base polimórficas 60 Item 8: Impeça que as exceções deixem destrutores 64 Item 9: Nunca chame funções virtuais durante a

construção ou a destruição 68 Item 10: Faça com que os operadores de atribuição

retornem uma referência para *this 72 Item 11: Trate as autoatribuições em operator= 73 Item 12: Copie todas as partes de um objeto 77

(15)

Capítulo 3 Gerenciamento de recursos

81

Item 13: Use objetos para gerenciar recursos 81 Item 14: Pense cuidadosamente no comportamento de

cópia em classes de gerenciamento de recursos 86 Item 15: Forneça acesso a recursos brutos em classes

de gerenciamento de recursos 89 Item 16: Use a mesma forma nos usos correspondentes

de new e delete 93

Item 17: Armazene objetos criados com new em ponteiros

espertos em sentenças autocontidas 95

Capítulo 4 Projetos e declarações

98

Item 18: Deixe as interfaces fáceis de usar corretamente e

difíceis de usar incorretamente 98 Item 19: Trate o projeto de classe como projeto de tipo 104 Item 20: Prefira a passagem por referência para constante

em vez da passagem por valor 106 Item 21: Não tente retornar uma referência quando

você deve retornar um objeto 110 Item 22: Declare os membros de dados como privados 115 Item 23: Prefira funções não membro e não amigas a

funções membro 118

Item 24: Declare funções não membro quando as conversões de tipo tiverem de ser aplicadas a

todos os parâmetros 122 Item 25: Considere o suporte para um swap que não

lance exceções 126

Capítulo 5 Implementações

133

Item 26: Postergue a definição de variáveis tanto quanto possível 133 Item 27: Minimize as conversões explícitas 136 Item 28: Evite retornar “manipuladores” para objetos internos 143 Item 29: Busque a criação de código seguro em

relação a exceções 147 Item 30: Entenda as vantagens e desvantagens da internalização 154 Item 31: Minimize as dependências de compilação

entre os arquivos 160

(16)

SUMÁRIO 19

Capítulo 6 Herança e projeto orientado a objetos

169

Item 32: Certifique-se de que a herança pública modele

um relacionamento “é um(a)” 170 Item 33: Evite ocultar nomes herdados 176 Item 34: Diferencie a herança de interface da herança

de implementação 181

Item 35: Considere alternativas ao uso de funções virtuais 189 Item 36: Nunca redefina uma função não virtual herdada 198 Item 37: Nunca redefina um valor padrão de parâmetro

herdado de uma função 200 Item 38: Modele “tem um(a)” ou “é implementado(a) em

termos de” com composição 204 Item 39: Use a herança privada com bom-senso 207 Item 40: Use a herança múltipla com bom-senso 212

Capítulo 7 Templates e programação genérica

219

Item 41: Entenda as interfaces implícitas e o polimorfismo

em tempo de compilação 219 Item 42: Entenda os dois significados de typename 223 Item 43: Saiba como acessar nomes em classes-base

com templates 227

Item 44: Fatore código independente de parâmetros a

partir de templates 232 Item 45: Use templates de funções membro para aceitar

“todos os tipos compatíveis” 238 Item 46: Defina funções não membro dentro de templates

quando desejar conversões de tipo 242 Item 47: Use classes de trait para informações sobre tipos 247 Item 48: Fique atento à metaprogramação por templates 253

Capítulo 8 Personalizando

new

e

delete

259

Item 49: Entenda o comportamento do tratador de new 260 Item 50: Entenda quando faz sentido substituir new e delete 267 Item 51: Adote a convenção quando estiver

(17)

Item 52: Escreva delete de posicionamento se escrever

new de posicionamento 276

Capítulo 9 Miscelânea

282

Item 53: Preste atenção aos avisos do compilador 282 Item 54: Familiarize-se com a biblioteca padrão, incluindo TR1 283 Item 55: Familiarize-se com Boost 289

Índice

293

(18)

Aprender os fundamentos de uma linguagem de programação é uma coisa; aprender como projetar e implementar programas eficientes nessa gem é algo completamente diferente, principalmente em C++, uma lingua-gem poderosa e expressiva. Se usada de forma correta, C++ pode ser ótima de trabalhar; com ela, uma enorme variedade de projetos pode ser expressa diretamente e implementada de maneira eficiente. Um conjunto de clas-ses, funções e templates, cuidadosamente escolhido e criado, pode deixar a programação de aplicações fácil, intuitiva, eficiente e praticamente livre de erros. Não é tão difícil escrever programas eficazes em C++ se você sabe como fazê-lo. Empregada sem disciplina, no entanto, C++ pode gerar códi-gos incompreensíveis, ineficientes, difíceis de serem mantidos e extendidos e, muitas vezes, errados.

O objetivo deste livro é mostrar como usar C++ de modo eficaz. Presumo que você já conheça C++ como linguagem e tenha alguma experiência em seu uso. Este é um guia para que a linguagem seja usada de forma que seus aplicativos de software sejam compreensíveis, fáceis de serem mantidos, portáveis, extensíveis, eficientes e tendam a se comportar como você espera. Os conselhos que dou caem em duas categorias amplas: estratégias gerais de projeto e funcionalidades específicas da linguagem e de seus recursos. As discussões de projeto concentram-se em como escolher entre diferentes abordagens para realizar algo em C++. Como você escolhe entre a herança e os templates? Entre a herança pública e a privada? Entre a herança pri-vada e a composição? Entre as funções membro e as não membro? Entre a passagem por valor e a passagem por referência? É importante tomar essas decisões corretamente no início, pois uma má escolha pode não ser aparen-te até muito tarde no processo de desenvolvimento, em um ponto no qual corrigir essa escolha normalmente é difícil, demanda tempo e é caro. Mesmo quando você sabe exatamente o que quer fazer, pode ser complicado conseguir as coisas da maneira certa. Qual é o tipo de retorno apropriado para os operadores de atribuição? Quando um destrutor deve ser virtual? Como operator new deve se comportar quando não consegue encontrar memória suficiente? É crucial tratar de detalhes como esses, pois não fazer

(19)

isso quase sempre leva a um comportamento de programa inesperado, mui-tas vezes obscuro. Este livro o ajudará a evitar isso.

Esta não é uma referência completa sobre C++, mas uma coleção de 55 sugestões específicas (chamadas de Itens) para melhorar seus programas e projetos. Cada item é independente, mas a maioria contém referências a ou-tros Itens. Uma maneira de ler o livro é começar com um item de interesse e seguir suas referências para onde elas o levarem.

Este livro também não é uma introdução a C++. O Capítulo 2, por exem-plo, discute tudo sobre as implementações adequadas de construtores, des-trutores e operadores de atribuição, mas presumo que você já conheça ou possa consultar outra referência para descobrir o que essas funções fazem e como elas são declaradas. Diversas obras sobre C++ contêm informações como essas.

O propósito deste livro é destacar aqueles aspectos da programação em C++ que muitas vezes não são vistos com o cuidado necessário; outros li-vros descrevem as diferentes partes da linguagem. Este diz como combinar essas partes de forma que você tenha programas eficazes; outros ensinam como fazer seus programas serem compilados. Esta obra discute como evi-tar problemas que os compiladores não dirão a você.

Ao mesmo tempo, este livro limita-se a C++ padrão: apenas recursos no padrão oficial da linguagem foram usados. A portabilidade é uma preocupa-ção muito importante aqui; se você está procurando truques que dependem de plataforma, este não é o lugar para encontrá-los.

Outra coisa que você não encontrará neste livro é a bíblia de C++, o único caminho verdadeiro para o desenvolvimento de software perfeito em C++. Todos os itens deste livro fornecem guias sobre como desenvolver projetos melhores, como evitar problemas comuns, ou como ter maior eficiência, mas nenhum é universalmente aplicável. O projeto e a implementação de software são tarefas complexas, dificultadas ainda mais pelas restrições de hardware, de sistema operacional e de aplicativo, então o melhor que posso fazer é fornecer recomendações para criar programas melhores.

Se você seguir todas as recomendações o tempo todo, provavelmente não cairá nas armadilhas mais comuns em C++, mas as recomendações, por sua natureza, têm exceções. É por isso que cada item tem uma explicação. As explicações são a parte mais importante do livro. Só entendendo o ra-ciocínio por trás de um item é que você pode determinar se ele se aplica ao sistema de software que você está desenvolvendo e às restrições que precisa atender.

O melhor uso deste livro é para saber como C++ se comporta, por que se comporta dessa maneira e como usar esse comportamento em seu

(20)

INTRODUÇÃO 23

cio. A aplicação cega dos itens deste livro é obviamente inadequada, mas, ao mesmo tempo, você provavelmente não deve violar as recomendações sem uma boa razão.

Terminologia

Existe um pequeno vocabulário C++ que todo programador deve entender. Os termos a seguir são suficientemente importantes para que concordemos quanto ao seu significado.

Uma declaração diz aos compiladores o nome e o tipo de algo, mas omite certos detalhes. Estes são exemplos de declarações:

extern int x; // declaração de objeto std::size_t numDigits(int number); // declaração de função class Widget; // declaração de classe template<typename T> // declaração de template

class GraphNode; // (veja o Item 42 para mais informações sobre // o uso de "typename")

Observe que me refiro ao inteiro x como um “objeto”, mesmo que ele seja de um tipo primitivo. Algumas pessoas reservam o nome “objeto” para variáveis de tipos definidos pelo usuário, mas não sou uma delas. Obser-ve também que o tipo de retorno da função numDigits é std::size_t, ou seja, o tipo size_t no espaço de nomes std. Esse espaço de nomes é onde se localiza praticamente tudo da biblioteca padrão de C++. Entre-tanto, como a biblioteca padrão de C (aquela de C89, para ser mais pre-ciso) também pode ser usada em C++, os símbolos herdados de C (como size_t) podem existir em escopo global, dentro de std, ou em ambos, dependendo de quais cabeçalhos foram incluídos (através de #include). Neste livro, considero que os cabeçalhos C++ foram incluídos, e é por isso que me refiro a std::size_t em vez de size_t. Quando me refi-ro aos componentes da biblioteca padrão, em geral omito referências a std, pois entendo que você reconhece que coisas como size_t, vector e cout estão em std. Em código de exemplos, sempre incluo std, porque o código real não será compilado sem isso.

A propósito, size_t é apenas uma definição de tipo para alguns tipos sem sinal que C++ usa quando está contando coisas (como o número de carac-teres em uma cadeia baseada em char*, o número de elementos em um contêiner STL, etc). Também é o tipo usado pelas funções operator[] em vector, deque e string, uma convenção que seguiremos quando estiver-mos definindo nossas próprias funções operator[] no Item 3.

A declaração de cada função revela sua assinatura, ou seja, seus tipos de parâmetros e de retorno. A assinatura de uma função é o mesmo que seu tipo. No caso de numDigits, a assinatura é std::size_t(int), ou seja, “uma função que recebe um int e retorna um std::size_t”. A definição oficial C++ de “assinatura” exclui o tipo de retorno da função,

(21)

mas, neste livro, é mais útil que o tipo de retorno seja considerado parte da assinatura.

Uma definição fornece aos compiladores os detalhes que uma declara-ção omite. Para um objeto, a definideclara-ção é onde os compiladores reservam memória para o objeto. Para uma função ou para um template de função, a definição fornece o corpo de código. Para uma classe ou para um tem-plate de classe, a definição lista os membros da classe ou do temtem-plate:

int x; // definição de objeto std::size_t numDigits(int number) // definição de função { // (Esta função retorna

std::size_t digitsSoFar = 1; // o número de dígitos // em seu parâmetro) while ((number /= 10) != 0) ++digitsSoFar;

return digitsSoFar; }

class Widget { // definição de classe public:

Widget( ); ~Widget( ); ... };

template<typename T> // definição de template class GraphNode { public: GraphNode( ); ~GraphNode( ); ... };

A inicialização é o processo de dar a um objeto seu primeiro valor. Para objetos de tipos definidos pelo usuário, a inicialização é realizada pelos construtores. Um construtor padrão é aquele que pode ser chamado sem argumentos. Esse construtor não tem parâmetros ou possui um valor pa-drão para cada um dos parâmetros:

class A { public: A( ); // construtor padrão }; class B { public:

explicit B(int x = 0, bool b = true); // construtor padrão; veja abaixo }; // para mais informações sobre "explicit" class C {

public:

explicit C(int x); // não é um construtor padrão };

Os construtores para as classes B e C são declarados como explícitos (explicit) aqui. Isso impede que sejam usados para realizar conversões

(22)

INTRODUÇÃO 25

de tipo implícitas, apesar de ainda poderem ser usados para conversões de tipo explícitas:

void doSomething(B bObject); // uma função que recebe um objeto do // tipo B

B bObj1; // um objeto do tipo B

doSomething(bObj1); // ok, passa um B para doSomething B bObj2(28); // ok, cria um B a partir do inteiro 28

// (o valor de bool é padronizado como verdadeiro) doSomething(28); // erro! doSomething recebe um B

// não é um int e não existe uma // conversão implícita de int para B doSomething(B(28)); // ok, usa o construtor de B para

// converter explicitamente (cast) o // int para um B para esta chamada. (Veja // o Item 27 para obter mais informações // sobre conversões explícitas)

Os construtores declarados como explícitos (explicit) normalmente são preferíveis aos não explícitos, porque impedem que os compiladores rea-lizem conversões de tipo inesperadas (frequentemente não desejadas). A menos que tenha uma boa razão para permitir que um construtor seja usa-do para conversões de tipo implícitas, o declaro como explícito. Incentivo o leitor a seguir a mesma política.

Observe como destaquei a conversão explícita no exemplo acima. Ao longo deste livro, uso esses destaques para chamar a sua atenção para o material que é digno de nota. (Também destaco os números de capítulos, mas ape-nas porque acho que fica bonito.)

O construtor de cópia é usado para inicializar um objeto com um objeto diferente do mesmo tipo, e o operador de atribuição por cópia é usado para copiar um valor de um objeto para outro do mesmo tipo:

class Widget { public:

Widget( ); // construtor padrão Widget(const Widget& rhs); // construtor de cópia

Widget& operator=(const Widget& rhs); // construtor de atribuição por cópia ...

};

Widget w1; // invoca o construtor padrão Widget w2(w1); // invoca o construtor de cópia

w1 = w2; // invoca o construtor de atribuição por cópia Quando enxergar o que aparenta ser uma atribuição, leia com muita aten-ção, porque a sintaxe “=” pode ser usada para chamar o construtor de có-pia:

(23)

Felizmente, a construção de cópia é fácil de distinguir da atribuição por có-pia. Se um novo objeto está sendo definido (tal como w3 na sentença acima), é necessário chamar um construtor; ele não pode ser atribuído. Se nenhum objeto novo estiver sendo definido (como a sentença “w1 = w2” acima), ne-nhum construtor pode ser envolvido, então é uma atribuição.

O construtor de cópia é uma função especialmente importante, porque define como um objeto é passado por valor. Por exemplo, considere o seguinte:

bool hasAcceptableQuality(Widget w); ...

Widget aWidget;

if (hasAcceptableQuality(aWidget)) ...

O parâmetro w é passado para hasAcceptableQuality por valor, então, na chamada acima, Widget é copiada para w. A cópia é feita pelo construtor de cópia de Widget. A passagem por valor significa “chame o construtor de cópia”. (Entretanto, passar tipos definidos pelo usuário por valor costuma ser uma má ideia. Passar por referência a const geralmente é uma escolha melhor. Para obter mais detalhes, veja o Item 20.)

A STL é a Biblioteca de Templates Padrão – Standard Template Li-brary –, a parte da biblioteca padrão a de C++ dedicada aos contêine-res (por exemplo, vector, list, set, map, etc), iteradocontêine-res (por exemplo, vector<int>::iterator, set<string>::iterator, etc), algoritmos (for_each, find, sort, etc) e funcionalidades relacionadas. Muitas dessas funcionalidades estão ligadas a objetos função: objetos que agem como fun-ções. Esses objetos vêm de classes que sobrecarregam operator(), o ope-rador de chamada da função. Se você não está familiarizado com a STL, é interessante ter uma referência decente disponível à medida que lê este livro, pois a STL é útil demais para que eu não tire proveito dela. Depois de usá-la um pouco, você pensará da mesma forma.

Os programadores que vêm de linguagens como Java ou C# podem se surpre-ender com a noção de comportamento indefinido. Por uma série de razões, o comportamento de algumas construções em C++ é literalmente indefinido: você não pode prever com certeza o que acontecerá em tempo de execução. Veja dois exemplos de código com comportamento indefinido:

int *p = 0; // p é um ponteiro nulo

std::cout << *p; // desreferenciar um ponteiro nulo

// leva a comportamento indefinido char name[ ] = “Darla”; // nome é um vetor de tamanho 6 (não

// se esqueça do nulo no final!)

char c = name[10]; // referencia um índice inválido de vetor

// leva a comportamento indefinido

Para enfatizar que os resultados de comportamentos indefinidos não são previsíveis e podem ser muito desagradáveis, programadores experientes

(24)

INTRODUÇÃO 27

frequentemente dizem que os programas com comportamento indefinido podem apagar seu disco rígido. É verdade: um programa com comporta-mento indefinido pode apagar seu disco rígido. Mas isso é pouco provável. É mais provável que o programa se comporte de maneira errática, algumas vezes rodando normalmente, outras travando, e ainda outras produzindo resultados incorretos. Programadores de C++ eficazes fazem o melhor pos-sível para se livrar de comportamentos indefinidos. Neste livro, destaco di-versos locais nos quais você precisa tomar cuidado com isso.

Outro termo que pode causar confusão para os programadores que vêm de outra linguagem é o termo interface. As linguagens Java e .NET fornecem interfaces como elemento de linguagem, mas isso não existe em C++, em-bora o Item 31 discuta como abordá-las. Quando uso o termo “interface”, geralmente estou falando da assinatura de uma função, sobre os elementos acessíveis de uma classe (por exemplo, a “interface pública”, a “interface protegida” ou a “interface privada” de uma classe) ou sobre expressões que devem ser válidas para um parâmetro de tipo de um template (veja o Item 41). Ou seja, estou falando de interfaces como uma ideia bem geral de pro-jeto.

Um cliente é alguém ou algo que usa o código (em geral, as interfaces) que você escreve. Os clientes de uma função, por exemplo, são seus usuários: as partes do código que chamam a função (ou que recebem seu endereço), bem como os seres humanos que escrevem e mantêm tal código. Os clientes de uma classe ou de um template são as partes de software que usam a classe ou o template, bem como os programadores que escrevem e mantêm esse código. Quando discuto clientes, normalmente me foco nos programadores, pois eles podem ser confundidos, enganados ou incomodados por interfa-ces ruins. O código que eles escrevem, não.

Você pode não estar acostumado a pensar nos clientes, mas passarei boa parte do tempo tentando convencê-lo a tornar a vida deles o mais fácil pos-sível. Você mesmo é um cliente dos sistemas de software que outras pessoas desenvolvem – não gostaria que essas pessoas tornassem as coisas fáceis para você? Além disso, em algum momento, você certamente se encontrará na posição de seu próprio cliente (ou seja, usando código que escreveu) e, nesse ponto, ficará feliz por ter se preocupado com os clientes quando esta-va desenvolvendo suas interfaces.

Neste livro, frequentemente ignoro as distinções entre funções e templates de funções e entre classes e templates de classes. Isso porque o que é verda-de para um normalmente é verdaverda-de para o outro. Nas situações em que esse não for o caso, faço distinção entre classes, funções e templates que fazem surgir classes e funções.

Ao me referir a construtores e destrutores em comentários de código, às vezes uso as abreviações ctor e dtor.

(25)

Convenções de nomenclatura

Tentei selecionar nomes significativos para objetos, classes, funções, tem-plates, etc., mas o significado por trás de alguns nomes pode não ser ime-diatamente visível. Dois dos meus nomes de parâmetros favoritos, por exemplo, são lhs e rhs. Eles significam “lado esquerdo” (left-hand side) e “lado direito” (right-hand side), respectivamente. É comum eu usá-los como nomes de parâmetros para funções que implementam operadores binários, como operator== e operator*. Por exemplo, se a e b são objetos que representam números racionais, e se os objetos racionais (Rational) pu-derem ser multiplicados por uma função não membro operator* (como explica o Item 24, esse provavelmente é o caso), a expressão

a * b

é equivalente à chamada a função operator*(a, b)

No Item 24, declaro operator* assim:

const Rational operator*(const Rational& lhs, const Rational& rhs);

Como você pode ver, o operando da esquerda, a, é conhecido como lhs den-tro da função, e o operando da direita, b, é conhecido como rhs.

Para as funções membro, o argumento da esquerda é representado pelo ponteiro this, assim, às vezes uso o nome do parâmetro rhs por si mes-mo. Talvez você tenha observado isso nas declarações para algumas funções membro de Widget na página 5. Frequentemente, uso a classe Widget nos exemplos; “Widget” não tem significado específico, é apenas um nome que uso quando preciso de um nome de classe de exemplo. Nada tem a ver com os widgets dos kits de ferramentas de interface gráfica com o usuário (GUIs). Também nomeio bastante os ponteiros seguindo a regra em que um ponteiro para um objeto do tipo T é chamado de pt, “ponteiro para T”. Veja alguns exemplos:

Widget *pw; // pw = ptr para Widget class Airplane;

Airplane *pa; // pa = ptr para Airplane class GameCharacter;

GameCharacter *pgc; // pgc = ponteiro para GameCharacter Uso uma convenção parecida para referências: rw pode ser uma referência para um Widget e ra uma referência para uma aeronave (Airplane). Às vezes, uso o nome mf quando estou falando sobre funções membro.

Considerações sobre linhas de execução (threads)

Como linguagem, C++ não tem a noção de linhas de execução – nenhuma noção de concorrência de qualquer tipo, na verdade. O mesmo ocorre com

(26)

INTRODUÇÃO 29

a biblioteca padrão de C++. No que diz respito a C++, não existem progra-mas com linhas de execução múltiplas.

Ainda assim elas existem. Meu foco, neste livro, é C++ padrão, portável, mas não posso ignorar o fato de que a segurança das linhas de execução é uma questão que deve ser tratada por muitos programadores. Minha abor-dagem para lidar com essa diferença entre o padrão C++ e a realidade é apontar os locais em que as construções de C++ examinadas podem cau-sar problemas em um ambiente com linhas de execução múltiplas. No en-tanto, este livro não é sobre programação com linhas de execução múltiplas em C++. Longe disso. Esta obra se foca em programação C++ e, embora em sua maior parte se limite a considerações que levam em conta uma linha de execução única, discute a existência de linhas de execução múltiplas. Ela também tenta apontar os locais específicos em que os programadores que precisam estar cientes das linhas de execução de um programa têm que tomar cuidado ao avaliar o conselho que ofereço.

Se você não está familiarizado com linhas de execução múltiplas ou não precisa se preocupar com isso, pode ignorar meus comentários sobre elas. Se estiver programando um aplicativo ou biblioteca com linhas de execução múltiplas, entretanto, lembre que meus comentários são um pouco mais do que um ponto de partida para as questões com as quais precisará lidar quando estiver usando C++.

TR1 e Boost

Você encontrará referências a TR1 e a Boost ao longo do livro. Cada um tem um item que o descreve com algum detalhamento (Item 54 para TR1 e Item 55 para Boost), mas, infelizmente, esses itens estão no final do livro. (Eles estão lá porque funciona melhor assim. Verdade. Tentei colocá-los em vários outros lugares, mas não deu.) Se quiser, você pode ir para esses itens e lê-los agora, mas se preferir começar o livro do início e não do fim, este resumo vai ajudá-lo:

• TR1 (Relatório Técnico 1 – Technical Report 1) é uma especifica-ção para novas funcionalidades adicionadas à biblioteca padrão de C++. Essas funcionalidades tomam a forma de classes e templates de funções novas para coisas como tabelas de dispersão, ponteiros espertos de contagem de referência, expressões regulares, e muito mais. Todos os componentes TR1 estão no espaço de nomes tr1 que está aninhado dentro do espaço de nomes std.

• Boost é uma organização e um site (http://boost.org) que oferece bibliotecas C++ portáveis, revisadas por pares e de código aberto. A maior parte da funcionalidade de TR1 é baseada no trabalho feito em Boost, e, até que os fornecedores de compiladores incluam TR1 em suas distribuições da biblioteca de C++, o site Boost provavelmente continuará sendo a primeira parada para os desenvolvedores em busca de implementações do TR1. Boost oferece mais do que está disponível em TR1, entretanto; assim, vale a pena conhecê-lo.

(27)

Seja qual for a sua experiência em programação, é provável que você demore um pouco para se acostumar com a linguagem C++. Trata-se de uma linguagem poderosa com uma enorme variedade de ferramentas, mas, para aproveitar todo esse poder e utilizar seus recursos de modo eficaz, é preciso adaptar-se ao modo C++ de fazer as coisas. Esse é o tema deste livro, e este capítulo aborda os aspectos mais importantes da linguagem.

Item 1: Pense em C++ como um conjunto de linguagens

No início, C++ era apenas C com alguns recursos de orientação a objetos incorporados. O próprio nome original da linguagem, “C com Classes”, refletia essa herança.

À medida que C++ amadurecia, a linguagem se tornava mais ousada e aventureira, adotando ideias, recursos e estratégias de programação dife-rentes das de C com Classes. As exceções exigiram abordagens difedife-rentes para estruturar as funções (veja o Item 29). Os templates fizeram surgir novas maneiras de pensar sobre o projeto (veja o Item 41), e a STL definiu uma visão para a extensibilidade diferente de tudo o que a maioria das pessoas já havia visto.

Hoje, C++ é uma linguagem de programação de múltiplos paradigmas, que suporta uma combinação de recursos procedurais orientados a obje-tos, funcionais, genéricos e de metaprogramação. Esse poder e flexibilidade tornam C++ uma ferramenta ímpar, mas também podem confundir – to-das as regras de “uso apropriado” parecem ter exceções. Como vamos en-tender essa linguagem?

A maneira mais fácil é ver C++ não como uma linguagem, mas como um conjunto de linguagens relacionadas. Dentro de uma sublinguagem específica, as regras tendem a ser simples, diretas e fáceis de lembrar. Quando você se move de uma sublinguagem para outra, entretanto, as regras podem mudar. Para entender C++, você precisa reconhecer suas sublinguagens principais. Felizmente, existem apenas quatro:

ACOSTUMANDO-SE COM

A LINGUAGEM C++

(28)

32 C++ EFICAZ: 55 MANEIRASDEAPRIMORARSEUSPROGRAMASEPROJETOS

• C. No fundo, C++ ainda é uma linguagem baseada em C. Os blocos, as sentenças, o pré-processador, os tipos de dados predefinidos, os vetores, os ponteiros, etc., são oriundos de C. Em muitos casos, C++ fornece abordagens para resolver problemas que são superiores às correspondentes em C – por exemplo, veja os Itens 2 (alternativas ao pré-processador) e 13 (usando objetos para gerenciar recursos). No entanto, quando você programa com a parte C de C++, as regras para programação eficaz refletem o escopo mais limitado de C: não há templates, não há exceções, não há sobrecarga, etc.

• C++ Orientada a Objetos. É a parte que C com Classes representa-va: classes (incluindo construtores e destrutores), encapsulamento, herança, polimorfismo, funções virtuais (vinculação dinâmica), etc. As regras clássicas de projeto orientado a objetos aplicam-se mais direta-mente nessa sublinguagem.

• C++ com Templates. É a parte de programação genérica de C++, aquela em que a maioria dos programadores tem menos experiência de uso. As considerações de templates permeiam C++, e é comum que as regras de boa programação incluam cláusulas especiais que se aplicam somente aos templates (por exemplo, veja o Item 46 sobre como facili-tar conversões de tipos em chamadas a funções templates). Na verdade, os templates são tão poderosos que deram origem a um paradigma de programação completamente novo, a metaprogramação por templates (TMP – template metaprogramming). O Item 48 oferece uma visão ge-ral da TMP, mas, a menos que você seja viciado em templates, não pre-cisa se preocupar com isso. As regras para TMP raramente interagem com a programação mais utilizada em C++.

• STL. A STL é uma biblioteca de templates muito especial. Suas con-venções sobre contêineres, iteradores, algoritmos e objetos função en-trelaçam-se com perfeição, mas os templates e as bibliotecas podem ser criados também em torno de outras ideias. A STL tem uma maneira específica de trabalhar: certifique-se de seguir suas convenções ao usar essa sublinguagem.

Mantenha essas quatro sublinguagens em mente e não se surpreenda ao encontrar situações em que uma programação eficaz exigir uma mudança de estratégia ao trocar de uma sublinguagem para outra. Por exemplo, a passagem por valor, em geral, é mais eficiente do que a passagem por refe-rência para tipos predefinidos (por exemplo, tipos de C), mas, quando você se move da parte C de C++ para C++ Orientada a Objetos, a existência de construtores e destrutores definidos pelos usuários normalmente torna a “passagem por referência para constante” uma opção melhor. Isso ocorre principalmente ao trabalhar com C++ com Templates, porque, nesse caso, você nem mesmo sabe o tipo de objeto de que estará tratando. Ao lidar com a STL, entretanto, você sabe que os iteradores e os objetos função são modelados como ponteiro em C; portanto, a regra antiga de C sobre a pas-sagem por valor se aplica novamente (para ver mais detalhes sobre como escolher dentre as opções de passagem de parâmetros, veja o Item 20).

(29)

C++, portanto, não é uma linguagem unificada com um conjunto único de regras: é uma união de quatro sublinguagens, cada uma com suas próprias convenções. Com essas sublinguagens em mente, C++ será muito mais fácil de entender.

Lembrete

» As regras de uma programação eficaz em C++ variam de acordo com a parte da linguagem que você está usando.

Item 2: Prefira constantes, enumerações e internalizações a

definições

Este item poderia ser intitulado “prefira o compilador ao pré-processa-dor”, pois #define pode ser tratado como se não fizesse parte da lingua-gem propriamente dita. Esse é um de seus problemas. Quando você faz algo como

#define ASPECT_RATIO 1.653

o nome simbólico ASPECT_RATIO (proporção de tela*) talvez nunca seja visto pelos compiladores; ele pode ser removido pelo pré-processador an-tes que o código-fonte chegue a um compilador. Como resultado, o nome ASPECT_RATIO talvez não entre na tabela de símbolos, o que pode ser con-fuso se você receber um erro durante a compilação envolvendo o uso da constante, pois a mensagem de erro talvez se referencie a 1.653, mas não a ASPECT_RATIO. Se ASPECT_RATIO fosse definido em um arquivo de cabe-çalho que você não escreveu, você não teria ideia de onde veio esse 1.653, e perderia tempo tentando rastreá-lo. Esse problema também pode aparecer em um depurador simbólico, porque, mais uma vez, o nome com o qual você está programando pode não estar na tabela de símbolos.

A solução é substituir a macro por uma constante:

const double AspectRatio = 1.653; // nomes em maiúsculo em geral são para // macros, logo a troca de nomes

Como uma constante de linguagem, AspectRatio é definitivamente visto pelos compiladores e, certamente, é inserido em suas tabelas de símbo-los. Além disso, no caso de uma constante de ponto flutuante (como nesse exemplo), o uso da constante pode levar a um código menor do que com um #define. Isso ocorre porque a substituição cega do pré-processador do nome de macro ASPECT_RATIO por 1.653 pode resultar em cópias múl-tiplas de 1.653 em seu código objeto, enquanto que o uso da constante AspectRatio nunca deve resultar em mais de uma cópia.

* N. de T.: “Aspect ratio” é uma constante numérica muito usada para representar a proporção de tela de uma imagem bidimensional.

(30)

34 C++ EFICAZ: 55 MANEIRASDEAPRIMORARSEUSPROGRAMASEPROJETOS

Quanto à substituição de #defines por constantes, vale a pena mencio-nar dois casos especiais. O primeiro é a definição de ponteiros constan-tes. Como as definições constantes em geral são colocadas em arquivos de cabeçalho (em que muitos arquivos-fonte diferentes vão incluí-los), é importante que o ponteiro seja declarado como constante (const), normalmente em acréscimo àquilo para o qual o ponteiro aponta. Para definir uma cadeia de caracteres (string) constante baseada em char* em um arquivo de cabeçalho, por exemplo, você precisa escrever const duas vezes:

const char * const authorName = "Scott Meyers";

Para ver uma discussão completa dos significados e usos de const, es-pecialmente em conjunto com ponteiros, veja o Item 3. Entretanto, vale a pena lembrar que, em geral, objetos string são preferíveis aos seus pro-genitores baseados em char*; por isso, o nome do autor (authorName) costuma ser melhor definido da seguinte forma:

const std::string authorName("Scott Meyers");

O segundo caso especial diz respeito às constantes específicas de uma determinada classe. Para limitar o escopo de uma constante a uma clas-se, você deve torná-la um membro, e, para garantir que existirá no má-ximo uma cópia da constante, você deve torná-la um membro estático:

class GamePlayer { private:

static const int NumTurns = 5; // declaração da constante int scores[NumTurns]; // uso da constante ...

};

O que você vê acima é uma declaração para o número de turnos (NumTurns), não uma definição. Normalmente, C++ requer que você forneça uma defini-ção para qualquer coisa que usar, mas constantes específicas de uma classe que são estáticas e de um tipo integral (por exemplo, inteiros, caracteres, tipos booleanos) são uma exceção. Desde que não pegue os endereços delas, você pode declará-las e usá-las sem fornecer uma definição. Se você obtiver o endereço de uma constante de classe, ou se o compilador insistir, incorre-tamente, em uma definição mesmo que você não obtenha o endereço, você deve fornecer uma definição separada, como esta:

const int GamePlayer::NumTurns; // definição de NumTurns; veja

// abaixo porque não é dado nenhum valor Você coloca isso em um arquivo de implementação, não em um arquivo de cabeçalho. Como o valor inicial de constantes de classes é fornecido quando a constante é declarada (por exemplo, NumTurns é inicializada como 5 quando ela é declarada), nenhum valor inicial é permitido no momento da definição.

Observe, a propósito, que não é possível criar uma constante específi-ca de classe usando #define, porque #defines não respeitam escopo. Quando se define uma macro, ela aparece para todo o resto da

(31)

com-pilação (a menos que exista um #undef em algum lugar no caminho). Isso significa que #define não só não pode ser usado para constantes específicas de classe, como também não pode ser usado para fornecer algum tipo de encapsulamento, ou seja, não existe um #define “priva-do”. Obviamente, os membros de dados constantes (const) podem ser encapsulados; NumTurns é encapsulado.

Os compiladores mais antigos podem não aceitar a sintaxe citada, por-que costumava ser ilegal fornecer um valor inicial para um membro de classe estático no momento de sua declaração. Além disso, as inicializa-ções na classe são permitidas apenas para tipos inteiros e constantes. Nos casos em que a sintaxe não pode ser usada, você coloca o valor ini-cial no momento da definição:

class CostEstimate { private:

static const double FudgeFactor; // a declaração de constante de classe estática ... // constant; vai no arquivo de cabeçalho };

const double // definação de classe estática

CostEstimate::FudgeFactor = 1.35; // constant; vai no arquivo de implementação Isso é tudo o que você precisa na maior parte do tempo. A única exceção é quando você precisa do valor de uma constante de classe durante a compi-lação da classe, tal como na declaração do vetor GamePlayer:scores (que representa os pontos do jogador, e em que os compiladores insistem em sa-ber o tamanho do vetor em tempo de compilação). Então, a maneira aceita para compensar os erros dos compiladores que (incorretamente) proíbem a especificação na classe de valores iniciais para constantes de classe inteiras estáticas é usar o que carinhosamente (e não pejorativamente) é conhecido como o “hack de enumeração”. Essa técnica aproveita-se do fato de os valo-res de um tipo enumerado poderem ser usados nos locais em que se espe-ram valores inteiros, então GamePlayer (classe que representa um jogador) poderia ser muito bem definida assim:

class GamePlayer { private:

enum { NumTurns = 5 }; // "hack de enumeração" — torna // NumTurns um nome simbólico para 5 int scores[NumTurns]; // OK

... };

Vale a pena saber sobre o hack de enumeração, por diversas razões. Pri-meiro, de certa maneira o hack de enumeração se comporta mais como #define do que com uma constante (const), e, algumas vezes, é isso o que você quer. Por exemplo, é permitido pegar o endereço de uma cons-tante, mas não é permitido pegar o endereço de uma enumeração, e, em geral, não é permitido pegar o endereço de um #define. Se você não quer deixar que as pessoas obtenham um ponteiro ou uma referência para uma de suas constantes inteiras, a enumeração é uma boa maneira de garantir essa restrição. (Para saber mais sobre como garantir

(32)

36 C++ EFICAZ: 55 MANEIRASDEAPRIMORARSEUSPROGRAMASEPROJETOS

ções de projeto por meio de decisões de codificação, consulte o Item 18.) Além disso, embora os bons compiladores não reservem espaço para objetos constantes de tipos inteiros (a menos que você crie um ponteiro ou uma referência para eles), compiladores medíocres podem fazê-lo, e talvez você não queira reservar memória para esses objetos. Como os #defines, as enumerações nunca resultam nesse tipo desnecessário de alocação de memória.

Uma segunda razão para conhecer o hack de enumeração é puramente pragmática. Diversos códigos o empregam, então, você precisa reconhe-cê-lo quando o vir. Na verdade, o hack de enumeração é uma técnica fun-damental para a metaprogramação por templates (veja Item 48).

Voltando ao pré-processador, outro (des)uso comum da diretiva #define é usá-la para implementar macros que se parecem com funções, mas que não incorrem na sobrecarga de uma chamada de função. Eis uma macro que chama uma função f com o maior dos argumentos da macro:

// chama f com o máximo entre a e b

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

Macros como essa possuem tantas desvantagens que só pensar nelas já é doloroso.

Sempre que você escrever esse tipo de macro, tem que se lembrar de co-locar todos os argumentos no corpo da macro entre parênteses. Caso con-trário, você pode ter problemas quando alguém chamar a macro com uma expressão. Mas, mesmo que você acerte isso, veja as coisas estranhas que podem acontecer:

int a = 5, b = 0;

CALL_WITH_MAX(++a, b); // a é incrementado duas vezes CALL_WITH_MAX(++a, b+10); // a é incrementado uma vez

Aqui, o número de vezes que a é incrementado antes de chamar f depende do que está sendo comparado a ele!

Felizmente, você não precisa continuar com essa macro sem sentido. Você pode obter toda a eficiência de uma macro, além de todo o comportamento previsível e a segurança de tipos de uma função regular, com o uso de um template para uma função internalizada (inline function) (veja o Item 30):

template<typename T> // como não sabemos o que é T, inline void callWithMax(const T& a, const T& b) // passamos de uma

{ // referência para uma f(a > b ? a : b); // constante – veja o Item 20 }

Esse template gera uma família completa de funções, e cada uma delas recebe dois objetos do mesmo tipo e chama f com o maior dos dois ob-jetos. Não há necessidade de usar parênteses para os parâmetros dentro do corpo da função, nem de se preocupar em avaliar parâmetros várias vezes, etc. Além disso, como callWithMax (chama com o máximo) é uma

(33)

função real, ela obedece às regras de escopo e de acesso. Por exemplo, faz todo o sentido falar sobre uma função internalizada que é privada a uma classe. Em geral, não é possível fazer isso com uma macro.

Dada a disponibilidade de constantes, enumerações e internalizações, sua necessidade de usar o pré-processador (especialmente #define) é reduzida, mas não desaparece. #include permanece essencial, e #ifdef/#ifndef continuam a ter papéis importantes no controle da compilação. Ainda não é hora de aposentar o pré-processador, mas, defi-nitivamente, você deve dar férias longas e frequentes a ele.

Lembretes

» Para as constantes simples, prefira objetos constantes ou enumerações a #defines.

» Para macros parecidas com funções, prefira funções internalizadas a #defines.

Item 3: Use const

sempre que possível

Uma coisa incrível sobre o uso de const é que ele permite especificar uma restrição semântica – um objeto em particular não deve ser modifi-cado –, e os compiladores garantem essa restrição. Ele permite que você comunique, tanto para os compiladores quanto para outros programa-dores, que um valor deve permanecer invariante. Sempre que for ver-dadeiro, você deve garantir que isso seja dito, porque, dessa forma, seu compilador ajuda a garantir que a restrição não seja violada.

A palavra-chave const é, evidentemente, versátil. Fora das classes, você pode usá-la para constantes de escopo global ou de escopo de espaço de nomes (veja o Item 2), bem como para objetos declarados estáticos (static) no escopo de um arquivo, de uma função ou de um bloco. Den-tro das classes, você pode usá-la para membros de dados estáticos e para não estáticos. Para os ponteiros, você pode especificar se o ponteiro pro-priamente dito é constante, se o dado para o qual ele aponta é constante, ambos os casos ou nenhum deles:

char greeting[ ] = "Hello";

char *p = greeting; // ponteiro não constante, // dado não constante const char *p = greeting; // ponteiro não constante,

// dado constante char * const p = greeting; // ponteiro constante,

// dado não constante const char * const p = greeting; // ponteiro constante,

// dado constante

(34)

38 C++ EFICAZ: 55 MANEIRASDEAPRIMORARSEUSPROGRAMASEPROJETOS

Essa sintaxe não é tão instável quanto possa parecer. Se a palavra const aparecer na esquerda do asterisco, o que é apontado é constante; se a pala-vra const aparecer na direita do asterisco, o ponteiro propriamente dito é constante; se const aparecer em ambos os lados, ambos são constantes.* Quando o que é apontado é constante, alguns programadores listam const antes do tipo; outros a listam após o tipo, mas antes do asterisco. Não existe diferença no significado, então as seguintes funções recebem o mesmo tipo de parâmetro:

void f1(const Widget *pw); // f1 recebe um ponteiro para // um objeto Widget constante void f2(Widget const *pw); // f2 também

Já que as duas formas existem em código real, você deve se acostumar a ambas.

Os iteradores da STL são modelados como ponteiros, então um iterador (iterator) age de forma bastante parecia com um ponteiro T*. Declarar um iterador como constante (iterator const) é como declarar um pon-teiro constante (ou seja, declarar um ponpon-teiro T* const): o iterador não tem permissão para apontar para algo diferente, mas o item para o qual está apontando pode ser modificado. Se você quer um iterador que aponte para algo que não pode ser modificado (ou seja, o análogo em STL para um ponteiro const T*), você quer um iterador constante (const_iterator):

std::vector<int> vec; ...

const std::vector<int>::iterator iter = // iter age como um T* const vec.begin( );

*iter = 10; // OK, modifica o item para o // qual o ponteiro está apontando ++iter; // erro! iter é constante std::vector<int>::const_iterator cIter = // clter age como T* const

vec.begin( );

*cIter = 10; // erro! clter é constante ++cIter; // ótimo, modifica clter Alguns dos usos mais poderosos de const vêm de sua aplicação às decla-rações de funções. Dentro de uma declaração de função, const pode se referir ao valor de retorno da função, aos parâmetros individuais e, para funções membro, à função como um todo.

Fazer uma função retornar um valor constante é, geralmente, inapropriado, mas, algumas vezes, fazer isso reduz incidência de erros do cliente sem abrir mão da segurança ou da eficiência. Por exemplo, considere a declaração da função operator* para números racionais que é explorada no Item 24:

class Rational { ... };

const Rational operator*(const Rational& lhs, const Rational& rhs);

* Algumas pessoas acham útil ler declarações de ponteiros da direita para a esquerda, por exemplo, para ler

Referências

Documentos relacionados

A maneira expositiva das manifestações patológicas evidencia a formação de fissuras devido a movimentação térmica das paredes, Figura 9, que não foram dimensionadas com

 Para os agentes físicos: ruído, calor, radiações ionizantes, condições hiperbáricas, não ionizantes, vibração, frio, e umidade, sendo os mesmos avaliados

Este trabalho se propõe a investigar as relações entre o jornalismo, a literatura e o cinema por meio da análise da obra O que é isso, companheiro?, de Fernando Gabeira (1979) e a sua

Com o objetivo de compreender como se efetivou a participação das educadoras - Maria Zuíla e Silva Moraes; Minerva Diaz de Sá Barreto - na criação dos diversos

Neste artigo discutimos alguns aspectos característicos da sociedade a que Bauman denominou de “líquida”, a partir de As cidades invisíveis, de Italo Calvino –uma obra 6 por

A liderança ao serviço inverte a pirâmide do poder; em vez das pessoas trabalharem para servir o líder, o líder existe para servir os outros. Exemplo:

Toxicidade para órgãos-alvo específicos - exposição repetida dados não disponíveis. Perigo de aspiração dados

Tudo começou com o Josué, um cara da FFLCH-História, que inclusive tinha mania de fazer uma oposição ferrada contra a minha gestão (dessa forma, alguns membros