Até onde ir com tipos primitivos digitados como int

14

Eu vi código C ++ como o seguinte com muitos typedefs.

Quais são os benefícios de usar muitos typedefs como este, em vez de usar primitivas C ++? Existe outra abordagem que também possa alcançar esses benefícios?

No final, todos os dados são armazenados na memória ou transmitidos pelo fio como bits e bytes, isso realmente importa?

types.h:

typedef int16_t Version;
typedef int32_t PacketLength;
typedef int32_t Identity;
typedef int32_t CabinetNumber;
typedef int64_t Time64;
typedef int64_t RFID;
typedef int64_t NetworkAddress;
typedef int64_t PathfinderAddress;
typedef int16_t PathfinderPan;
typedef int16_t PathfinderChannel;
typedef int64_t HandsetSerialNumber;
typedef int16_t PinNumber;
typedef int16_t LoggingInterval;
typedef int16_t DelayMinutes;
typedef int16_t ReminderDelayMinutes;
typedef int16_t EscalationDelayMinutes;
typedef float CalibrationOffset;
typedef float AnalogValue;
typedef int8_t PathfinderEtrx;
typedef int8_t DampingFactor;
typedef int8_t RankNumber;
typedef int8_t SlavePort;
typedef int8_t EventLevel;
typedef int8_t Percent;
typedef int8_t SensorNumber;
typedef int8_t RoleCode;
typedef int8_t Hour;
typedef int8_t Minute;
typedef int8_t Second;
typedef int8_t Day;
typedef int8_t Month;
typedef int16_t Year;
typedef int8_t EscalationLevel;

Parece lógico tentar garantir que o mesmo tipo seja sempre usado para uma coisa específica, a fim de evitar estouros, mas geralmente vejo código em que "int" foi usado praticamente em todo lugar. O typedefing geralmente leva a um código que se parece um pouco com isso:

DoSomething(EscalationLevel escalationLevel) {
    ...
}

O que me faz pensar em qual token está realmente descrevendo o parâmetro: o tipo ou o nome do parâmetro?

Marca
fonte
2
IMHO, parece ser um exercício bastante inútil, mas eu tenho certeza que alguns outros que discordam ...
Nim
1
Esses tipos se parecem com nomes de variáveis.
Captain Giraffe
11
Observe que isso cria a impressão de que é seguro para o tipo, mas não é de todo - os typedefs apenas criam aliases, mas nada impede que você passe, por exemplo, a Minutepara uma função que possui um argumento declarado como tipo Second.
Jesper
2
@ Mark: olhe de outra maneira. Se você cometer um erro ao decidir o tipo inteiro, ou se surgirem novos requisitos no futuro, e desejar alterá-lo, gostaria de alterar um único typedef ou gostaria de pesquisar no código todas as funções que manipulam um ano e mudar de assinatura? 640k é suficiente para qualquer pessoa, e tudo isso. A desvantagem correspondente ao typedef é que as pessoas escrevem acidental ou deliberadamente um código que se baseia no fato de que Year é exatamente 16 bits e, em seguida, ele muda e seu código é quebrado.
precisa
1
@ Steve Jessop: Não consigo decidir se você acha que é uma boa ou má idéia :-) A primeira parte parece ser a favor, a segunda contra. Eu acho que tem prós e contras então.

Respostas:

13

O nome de um parâmetro deve descrever o que isso significa - no seu caso, o nível de escalação. O tipo é como o valor é representado - adicionar typedefs, como no seu exemplo, ofusca essa parte da assinatura da função, então eu não o recomendaria.

Typedefs são úteis para modelos, ou se você deseja alterar o tipo usado para determinados parâmetros, por exemplo, ao migrar de uma plataforma de 32 bits para uma de 64 bits.

Björn Pollex
fonte
Este parece ser o consenso geral então. Então você geralmente se ateria ao "int"? Eu tenho que ser muito eficiente neste aplicativo quando se trata de transferir dados, então seria apenas um caso de conversão para int16_t (ou qualquer que seja o maior tipo de representação necessário para um elemento em particular) durante a serialização?
@ Mark: Sim, é assim que você deve fazê-lo. Use typedefs para expressar o tamanho dos tipos de dados usados, mas não diferencie o mesmo tipo usado em contextos diferentes.
Björn Pollex
Obrigado - e apenas para esclarecer, você não se incomodaria em usar int8_t em vez de int geralmente no código .. Suponho que minha principal preocupação fosse algo como "Identidade", que na verdade é uma identidade gerada por um banco de dados. No momento, são 32 bits, mas ainda não tenho certeza se isso pode se tornar 64 bits. Além disso, que tal int32_t vs int? int geralmente é o mesmo que int32_t, mas nem sempre pode ser, eu acho, em uma plataforma diferente? Eu estou pensando que eu deveria se ater apenas ao "int" em geral, e "int64_t" onde neccessary .. obrigado :-)
@ Mark: O importante dos typedefs int32_té que você precisa se certificar de que eles estão corretos ao compilar em plataformas diferentes. Se você espera que o intervalo Identitymude em algum momento, acho que preferiria fazer as alterações diretamente em todo o código afetado. Mas não tenho certeza, porque precisaria saber mais sobre seu design específico. Você pode fazer disso uma pergunta separada.
Björn Pollex
17

No começo, pensei "Por que não", mas me ocorreu que, se você se esforçar tanto para separar os tipos assim, use melhor o idioma. Em vez de usar aliases, defina realmente os tipos:

class AnalogueValue
{
public:
    // constructors, setters, getters, etc..
private:
    float m_value;
};

Não há diferença de desempenho entre:

typedef float AnalogueValue;
AnalogValue a = 3.0f;
CallSomeFunction (a);

e:

AnalogValue a (3.0f); // class version
CallSomeFunction (a);

e você também tem as vantagens de adicionar validação de parâmetro e segurança de tipo. Por exemplo, considere o código que lida com dinheiro usando tipos primitivos:

float amount = 10.00;
CallSomeFunction(amount);

Além dos problemas de arredondamento, ele também permite qualquer tipo que possa ser convertido em um flutuador:

int amount = 10;
CallSomeFunction(amount);

Nesse caso, não é grande coisa, mas as conversões implícitas podem ser uma fonte de erros difíceis de identificar. Usar a typedefnão ajuda aqui, pois eles são apenas um alias de tipo.

Usar um novo tipo inteiramente significa que não há conversões implícitas, a menos que você codifique um operador de conversão, o que é uma má ideia especificamente porque permite conversões implícitas. Você também pode encapsular dados adicionais:

class Money {
  Decimal amount;
  Currency currency;
};

Money m(Decimal("10.00"), Currency.USD);
CallSomeFunction(m);

Nada mais se encaixará nessa função, a menos que escrevamos código para que isso aconteça. Conversões acidentais são impossíveis. Também podemos escrever tipos mais complexos, conforme necessário, sem muito aborrecimento.

Skizz
fonte
1
Você pode até escrever uma macro horrível para criar toda essa criação de classe para você. (Vamos, Skizz Você sabe que você quer..)
1
Para algumas delas, uma biblioteca de unidades com segurança de tipo pode ser útil ( tuoml.sourceforge.net/html/scalar/scalar.html ), em vez de escrever uma classe personalizada para cada uma.
precisa
@ Chris - Definitivamente não é uma macro, mas possivelmente uma classe de modelo. Como Steve ressalta, essas aulas já estão escritas.
precisa
4
@ Chris: A macro é chamado BOOST_STRONG_TYPEDEFrealmente;)
Matthieu M.
3

O uso de typedefs para tipos primitivos assim se parece mais com o código de estilo C.

No C ++, você receberá erros interessantes assim que tentar sobrecarregar funções para, digamos, EventLevele Hour. Isso torna os nomes de tipo extra bastante inúteis.

Bo Persson
fonte
2

Nós (em nossa empresa) fazemos muito em C ++. Ajuda a entender e manter o código. O que é bom ao mover pessoas entre equipes ou refatorar. Exemplo:

typedef float Price;
typedef int64_t JavaTimestmap;

void f(JavaTimestamp begin, JavaTimestamp end, Price income);

Acreditamos que é uma boa prática criar typedef para o nome da dimensão a partir do tipo de representação. Esse novo nome representa uma função geral em um software. O nome de um parâmetro é uma função local . Como em User sender, User receiver. Em alguns lugares, pode ser redundante void register(User user), mas não considero um problema.

Posteriormente, pode-se ter a idéia de que floatnão é o melhor para representar preços por causa das regras especiais de arredondamento da reserva; portanto, é possível baixar ou implementar um tipo BCDFloat(decimal codificado binário) e alterar o typedef. Não há pesquisa e substituir o trabalho de floatpara BCDFloatque se endureça pelo fato de que existem possivelmente muitos mais carros alegóricos em seu código.

Não é uma bala de prata e tem suas próprias advertências, mas achamos que é muito melhor usá-la do que não.

Não está na lista
fonte
Ou, como Mathieu M. sugeriu no cargo da Skizz, pode-se BOOST_STRONG_TYPEDEF(float, Price)dizer: mas eu não iria tão longe em um projeto comum. Ou talvez eu faria. Eu tenho que dormir nisso. :-)
Notinlist
1

typedefbasicamente permite que você dê um alias para um type.
Oferece a flexibilidade de evitar a digitação longatype names repetida e repetida e tornar sua typeleitura mais fácil, em que o nome alternativo indica a intenção ou o objetivo da type.

É mais uma questão de escolha se você deseja ter nomes mais legíveis typedefno seu projeto.
Normalmente, evito usar typedeftipos primitivos, a menos que sejam muito longos para serem digitados. Eu mantenho meus nomes de parâmetro mais indicativos.

Alok Save
fonte
0

Eu nunca faria algo assim. Certificar-se de que todos tenham o mesmo tamanho é uma coisa - mas você só precisa se referir a eles como tipos integrais.

DeadMG
fonte
0

Usar typedefs como este é bom, desde que quem os use não precise saber nada sobre sua representação subjacente . Por exemplo, se você deseja passar um PacketLengthobjeto para um printfou outroscanf , precisará conhecer seu tipo real para poder escolher o especificador de conversão correto. Em casos como esse, o typedef apenas adiciona um nível de ofuscação sem comprar nada em troca; você também pode ter definido o objeto como int32_t.

Se você precisar aplicar semântica específica para cada tipo (como intervalos ou valores permitidos), é melhor criar um tipo de dados e funções abstratos para operar com esse tipo, em vez de apenas criar um typedef.

John Bode
fonte