| Regras e
recomendações |
| 18 - Código portável |
|
- 18.1 - Abstração de dados
- 18.2 - Tamanhos dos tipos de dados
- 18.3 - Conversão de tipos
- 18.4 - Representação dos dados
- 18.5 - Underflow/overflow
- 18.6 - Ordem de execução
- 18.7 - Objetos temporários
- 18.8 - Aritmética de pointers
|
|
|
| 18.1 -
Abstração de dados |
|
Portabilidade.
Recomendações:
- 1: Evite o uso direto de tipos de dados
pré-definidos nas declarações
Uma excelente maneira de transformar o seu mundo em um vale
de lágrimas é usar diretamente tipos de dados pré-definidos nas declarações. Se mais
tarde, devido a problemas de portabilidade, for preciso alterar o tipo de retorno de uma
função, poderá ser necessário fazer alterações em um grande número de lugares no
código. Uma maneira de evitar isso é declarar um novo tipo usando classes ou typedef para
representar os tipos de variáveis usadas. Dessa maneira, alterações futuras se
tornarão mais fáceis. Isso pode ser usado para atribuir aos dados uma unidade física,
como kilograma ou metro. Um código assim é mais facilmente revisavel.
Por exemplo, quando o código funcionar mal, pode-se
perceber mais facilmente que uma variável representando metros foi atribuída a uma outra
variável que representa kilogramas. Perceba que typedef não cria um novo tipo, é
apenas um nome alternativo para o tipo. Ou seja, se você declarar typedef int Error, uma variável tipo
Error poderá ser usada em qualquer lugar onde um int puder
ser usado.
Exemplo 67: Declarações de
tipo usando typedef |
|
// Instead of:
long int time;
short int mouseX;
char* menuName;
// Use (for example):
typedef long int TimeStamp;
typedef short int Coordinate;
class String { /* ... */ };
// and:
TimeStamp time;
Coordinate mouseX;
String menuName;
|
|
|
| 18.2 - Tamanhos dos
tipos de dados |
|
Portabilidade. Recomendações:
- 2: Não assuma que um int e um long tem o mesmo tamanho
- 3: Não assuma que um int tem 32 bits de tamanho, porque pode ter apenas 16 bits de tamanho)
- 4: Não assuma que char é signed ou unsigned
- 5: Sempre determine unsigned char se estiver usando ASCII 8-bits
Na definição da linguagem C++, ainda não está decidido
se um char é signed ou unsigned. Essa decisão tem sido deixada para os fornecedores de
compiladores. Se você se esquecer disso, alguns bugs de
difícil localização poderão aparecer no programa quando um outro compilador for
utilizado.
Quando usando ASCII 8-bits, é importante usar unsigned char para poder fazer comparações entre dois caracteres . |
|
|
|
| 18.3 - Conversão de
tipos |
|
Portabilidade. Recomendações:
- 6: Tenha cuidado para não fazer conversões
de tipo de um shorter para um longer
- 7: Não assuma que pointers e inteiros tem o
mesmo tamanho
- 8: Use conversão explícita de tipos para
valores aritiméticos signed ou unsigned
A arquitetura dos processadores normalmente
proíbem que
dados de um determinado tamanho sejam alocados a um endereço de memória qualquer. Por
exemplo, uma palavra deve começar em um endereço par para o processador MC680x0. Se um pointer para um char for alocado em um
endereço impar, uma conversão desse pointer char para um pointer int vai
causar uma queda do programa quando o pointer int for usado, porque isso viola uma
regra do processador para alinhamento de dados. |
|
|
|
| 18.4 - Representação
dos dados |
|
Portabilidade. Recomendações:
- 9: Não assuma que você sabe como uma instância de
um tipo de dados é representada na memória
- 10: Não assuma que longs, floats, doubles ou
long doubles podem começar em um endereço de
memória qualquer.
A representação de tipos de dados na memória é altamente
dependente da máquina. O processador pode executar o código mais eficientemente alocando
os dados em endereços com certas características. Por isso, a estrutura de dados que
representa uma classe poderá, algumas vezes, incluir espaços vagos, e ser armazenada
diferentemente em diferentes arquiteturas de processadores. Código
que dependa de uma representação interna específica se torna então não portável. |
|
|
|
| 18.5 -
Underflow/overflow |
|
Portabilidade. Recomendações:
- 11: Não dependa, de nenhuma maneira, do
funcionamento de underflow ou overflow
|
|
|
|
| 18.6 - Ordem de
execução |
|
Portabilidade. Recomendações:
- 12: Não assuma que os operandos em uma
expressão serão resolvidos em uma ordem definitiva.
- 13: Não assuma que você sabe como são
implementados os mecanismos de invocação de funções
- 14: Não assuma que um objeto é
inicializado no construtor em qualquer ordem especial
- 15: Não assuma que objetos estáticos são
incializados em qualquer ordem especial
Se um valor for modificado duas vezes na mesma expressão,
o resultado da expressão pode ser indefinido, exceto se a ordem de resolução for
garantida pelos operadores usados.
A ordem de inicialização dos objetos estáticos pode
apresentar problemas. Um objeto estático não pode ser usado em um construtor se não
estiver inicializado o momeno em que o construtor for executado.
Exemplo 68: Não dependa da
ordem de inicialização em construtores |
|
#include
class X
{
public:
X(int y);
private:
int i;
int j;
};
inline X::X(int y) : j(y), i(j) // No! j may not be initialized before i !!
{
cout <<"i:" << i << " & " << "j:" << j /lt< endl; } main() { X x(7); // Rather unexpected output: i:0 & j:7 }
|
| Exemplo 69: Inicialização
de objetos estáticos |
|
// Foo.hh
#include
#include
static unsigned int const Size = 1024;
class Foo
{
public:
Foo( char* cp ); // Constructor
// ...
private:
char buffer[Size];
static unsigned counter; // Number of constructed Foo:s
};
extern Foo foo_1;
extern Foo foo_2;
// Foo1.cc
#include "Foo.hh"
unsigned Foo::counter = 0;
Foo foo_1 = "one";
//Foo2.cc
#include "Foo.hh"
Foo foo_2 = "two";
Foo::Foo( char* cp ) // Irrational constructor
{
strncpy( buffer, cp, sizeof(buffer) );
foos[counter] = this;
switch ( counter++ )
{
case 0:
case 1:
cout <<::foo_1.buffer << "," << ::foo_2.buffer << endl; break; default: cout << "Hello, world" << endl; } } // If a program using Foo.hh is linked with Foo1.o and Foo2.o, either // ,two or one, is written on standard output depending on // one,two one,two the order of the files given to the linker.
|
|
|
| 18.7 -
Objetos temporários |
|
Portabilidade. Recomendações:
- 16: Não escreva código dependente do tempo
de vida de um objeto temporário
Objetos temporários são criados com freqüência em C++,
como quando uma função retorna um valor. Podem surgir erros difíceis de se localizar
quando há pointers em objetos temporários. Já que a linguagem não define a
expectativa
de vida de objetos temporários, nunca se sabe com certeza se esses pointers ainda são
válidos no momento em que são usados.
Uma maneira de evitar esse problema é assegurar-se de que
esses objetos temporários não sejam criados. Esse método, entretanto, é limitado pelo
expressivo poder da linguagem é geralmente não é recomendável.
Exemplo 70: Erro difícil em
uma classe string que carece um operador de saída |
|
class String
{
public:
operator const char*() const; // Conversion operator to const char*
friend String operator+( const String& left, const String& right );
// ...
};
String a = "This may go to ";
String b = "h***!";
// The addition of a and b generates a new temporary String object.
// After it is converted to a char* by the conversion operator, it is
// no longer needed and may be deallocated. This means that characters
// which are already deallocated are printed to cout -> DANGEROUS!!
cout <
|
|
|
| 18.8 -
Aritmética de pointers |
|
Portabilidade. Recomendações:
- 17: Evite usar operações de shift em lugar
de operações aritméticas
- 18: Evite operações aritméticas com
pointers
Operações aritméticas com pointers podem ser portáveis.
Os operadores == e != são definidos para todos os pointers do mesmo tipo, os operadores <, >, <=, => são
portáveis apenas quando usados entre pointers que apontam para a mesma matriz. |
|
|
|