Por que usar prefixos em variáveis ​​de membro em classes C ++

150

Muitos códigos C ++ usam convenções sintáticas para marcar variáveis ​​de membro. Exemplos comuns incluem

  • m_ memberName para membros públicos (onde os membros públicos são usados)
  • _ memberName para membros privados ou todos os membros

Outros tentam impor o uso deste-> membro sempre que uma variável de membro é usada.

Na minha experiência, a maioria das bases de código maiores falha ao aplicar essas regras de forma consistente.

Em outros idiomas, essas convenções são muito menos difundidas. Só o vejo ocasionalmente em código Java ou C #. Eu acho que nunca vi isso no código Ruby ou Python. Portanto, parece haver uma tendência com linguagens mais modernas para não usar marcação especial para variáveis-membro.

Essa convenção ainda é útil hoje em C ++ ou é apenas um anacronismo? Especialmente porque é usado de maneira inconsistente nas bibliotecas. Os outros idiomas não mostraram que se pode prescindir de prefixos de membros?

VoidPointer
fonte
15
Eu prefiro; em bases de código complexas, pode ser importante saber quais vars são locais e quais não são. Eu geralmente uso o prefixo sobre forçando this-> que eu acho ser um monte de digitação extra e opcional (enquanto nomeação vai forçá-lo a fazê-lo)
Joe
5
Você nunca viu isso no Ruby por causa do atributo @ for, e pelo idioma de gerar acessadores em vez de usar atributos diretamente.
Steve Jessop
6
De acordo com o PEP 8 , as variáveis ​​de membro não público devem ser prefixadas com um sublinhado em Python (exemplo:) self._something = 1.
precisa
2
O realce da sintaxe do editor não deve ser usado para identificá-los?
também
2
Você viu o equivalente this->memberno código Python. Em Python, normalmente seria self.membere não é apenas uma convenção, é exigido pela linguagem.
matec 14/05

Respostas:

48

Você precisa ter cuidado ao usar um sublinhado à esquerda. Um sublinhado à esquerda antes de uma letra maiúscula em uma palavra ser reservada. Por exemplo:

_Foo

_EU

são todas as palavras reservadas enquanto

_foo

_eu

não são. Há outras situações em que sublinhados à esquerda antes de letras minúsculas não são permitidos. No meu caso específico, achei o _L reservado pelo Visual C ++ 2005 e o conflito criou alguns resultados inesperados.

Estou em dúvida sobre o quanto é útil marcar variáveis ​​locais.

Aqui está um link sobre quais identificadores são reservados: Quais são as regras sobre o uso de um sublinhado em um identificador C ++?

Juan
fonte
4
Na verdade, _foo e _l são reservados no escopo do espaço para nome.
13
Mas eles são válidos como nomes de variáveis ​​de membros. Não prefixo sublinhados, porque as regras são muito confusas e eu havia me queimado no passado.
Juan
11
Estas não são palavras reservadas. Eles são nomes reservados. Se fossem palavras reservadas, você não poderia usá-las. Como são nomes reservados, você pode usá-los, mas por seu próprio risco.
TonyK
235

Sou a favor de prefixos bem feitos .

Eu acho que a notação húngara (do sistema) é responsável pela maior parte do "mau rap" que os prefixos recebem.

Essa notação é inútil em linguagens fortemente tipadas, por exemplo, em C ++ "lpsz" para dizer que sua string é um ponteiro longo para uma string terminada em nulo, quando: arquitetura segmentada é história antiga, as strings C ++ são por ponteiros de convenção comuns para terminação nula matrizes de caracteres e não é tão difícil saber que "customerName" é uma string!

No entanto, eu uso prefixos para especificar o uso de uma variável (essencialmente "Aplicativos Húngaro", embora prefira evitar o termo Húngaro por ter uma associação ruim e injusta com o Sistema Húngaro), e esse é um método muito útil para economizar tempo e abordagem de redução de erros .

Eu uso:

  • m para membros
  • c para constantes / readonlys
  • p para ponteiro (e pp para ponteiro para ponteiro)
  • v para volátil
  • s para estática
  • i para índices e iteradores
  • e para eventos

Onde desejo esclarecer o tipo , uso sufixos padrão (por exemplo, List, ComboBox, etc).

Isso torna o programador ciente do uso da variável sempre que a vê / usa. Provavelmente, o caso mais importante é "p" para ponteiro (porque o uso muda de var. Para var-> e você precisa ter muito mais cuidado com ponteiros - NULLs, aritmética de ponteiros etc.), mas todos os outros são muito úteis.

Por exemplo, você pode usar o mesmo nome de variável de várias maneiras em uma única função: (aqui um exemplo de C ++, mas se aplica igualmente a muitos idiomas)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

Você pode ver aqui:

  • Nenhuma confusão entre membro e parâmetro
  • Nenhuma confusão entre índice / iterador e itens
  • Uso de um conjunto de variáveis ​​claramente relacionadas (lista de itens, ponteiro e índice) que evitam as muitas armadilhas de nomes genéricos (vagos) como "count", "index".
  • Prefixos reduzem a digitação (mais curta e funcionam melhor com o preenchimento automático) do que alternativas como "itemIndex" e "itemPtr"

Outro ponto importante dos iteradores "iName" é que nunca indexo uma matriz com o índice errado e, se eu copiar um loop dentro de outro loop, não preciso refatorar uma das variáveis ​​de índice do loop.

Compare este exemplo irrealisticamente simples:

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(difícil de ler e geralmente leva ao uso de "i" onde "j" foi planejado)

com:

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(que é muito mais legível e elimina toda a confusão sobre a indexação. Com o preenchimento automático nos IDEs modernos, isso também é rápido e fácil de digitar)

O próximo benefício é que os trechos de código não exigem que nenhum contexto seja entendido. Posso copiar duas linhas de código em um e-mail ou documento, e qualquer pessoa que esteja lendo esse trecho pode dizer a diferença entre todos os membros, constantes, ponteiros, índices etc. Não preciso adicionar "ah, e tenha cuidado porque 'data' é um ponteiro para um ponteiro ", porque é chamado 'ppData'.

E pelo mesmo motivo, não preciso desviar os olhos de uma linha de código para entender. Não preciso pesquisar o código para descobrir se 'data' é um local, parâmetro, membro ou constante. Não preciso mover minha mão para o mouse para poder passar o ponteiro do mouse sobre 'dados' e esperar uma dica (que às vezes nunca aparece) aparecer. Assim, os programadores podem ler e entender o código significativamente mais rápido, porque não perdem tempo pesquisando para cima e para baixo ou esperando.

(Se você não acha que está perdendo tempo pesquisando de cima para baixo para descobrir coisas, encontre um código que você escreveu há um ano e que não o examinou desde então. Abra o arquivo e pule pela metade do caminho sem lê-lo. Veja como até agora você pode ler a partir deste ponto antes de não saber se algo é um membro, parâmetro ou local.Agora pule para outro local aleatório ... É isso que todos nós fazemos o dia inteiro quando estamos percorrendo o código de outra pessoa ou tentando entender como chamar sua função)

O prefixo 'm' também evita a notação feia e prolixo "this->", e a inconsistência que ela garante (mesmo se você for cuidadoso, geralmente acabará com uma mistura de "this-> data" e 'data' na mesma classe, porque nada impõe uma ortografia consistente do nome).

A notação 'this' tem como objetivo resolver a ambiguidade - mas por que alguém escreveria deliberadamente um código que pode ser ambíguo? Ambigüidade vai levar a um bug, mais cedo ou mais tarde. E em alguns idiomas 'this' não pode ser usado para membros estáticos, então você deve introduzir 'casos especiais' no seu estilo de codificação. Prefiro ter uma única regra de codificação simples que se aplique a todos os lugares - explícita, inequívoca e consistente.

O último grande benefício é com o Intellisense e o preenchimento automático. Tente usar o Intellisense em um Windows Form para encontrar um evento - você precisa percorrer centenas de métodos misteriosos de classe base que você nunca precisará chamar para encontrar os eventos. Mas se cada evento tivesse um prefixo "e", eles seriam listados automaticamente em um grupo em "e". Assim, a prefixação trabalha para agrupar os membros, consts, eventos etc. na lista do intellisense, tornando muito mais rápido e fácil encontrar os nomes que você deseja. (Geralmente, um método pode ter entre 20 e 50 valores (locais, parâmetros, membros, consts, eventos) acessíveis em seu escopo. Mas, depois de digitar o prefixo (eu quero usar um índice agora, digito 'i. .. '), são apresentadas apenas 2-5 opções de preenchimento automático. A' digitação extra '

Sou um programador preguiçoso, e a convenção acima me poupa muito trabalho. Posso codificar mais rapidamente e cometo muito menos erros, porque sei como todas as variáveis ​​devem ser usadas.


Argumentos contra

Então, quais são os contras? Os argumentos típicos contra prefixos são:

  • "Esquemas de prefixo são ruins / maus" . Concordo que "m_lpsz" e seu tipo são mal pensados ​​e totalmente inúteis. É por isso que aconselho usar uma notação bem projetada, projetada para suportar seus requisitos, em vez de copiar algo inapropriado para o seu contexto. (Use a ferramenta certa para o trabalho).

  • "Se eu mudar o uso de algo, tenho que renomeá-lo" . Sim, é claro que sim, é disso que trata a refatoração e por que os IDEs têm ferramentas de refatoração para fazer esse trabalho de maneira rápida e indolor. Mesmo sem prefixos, alterar o uso de uma variável quase certamente significa que seu nome deve ser alterado.

  • "Prefixos me confundem" . Como todas as ferramentas até você aprender a usá-lo. Quando seu cérebro se acostumar com os padrões de nomeação, ele filtrará as informações automaticamente e você não se importará mais com os prefixos. Mas você tem que usar um esquema como esse solidamente por uma semana ou duas antes de se tornar realmente "fluente". E é aí que muitas pessoas olham para o código antigo e começam a se perguntar como conseguiram sem um bom esquema de prefixo.

  • "Eu posso apenas olhar o código para resolver essas coisas" . Sim, mas você não precisa perder tempo procurando em outro lugar no código ou lembrando-se de cada pequeno detalhe quando a resposta é certa no local em que seu olho já está focado.

  • (Algumas) informações podem ser encontradas apenas aguardando uma dica de ferramenta aparecer na minha variável . Sim. Onde houver suporte, para alguns tipos de prefixo, quando o código for compilado corretamente, após uma espera, você poderá ler uma descrição e encontrar as informações que o prefixo teria transmitido instantaneamente. Eu sinto que o prefixo é uma abordagem mais simples, mais confiável e mais eficiente.

  • "É mais digitado" . Realmente? Mais um personagem inteiro? Ou é: com as ferramentas de preenchimento automático do IDE, muitas vezes reduz a digitação, porque cada caractere de prefixo reduz significativamente o espaço de pesquisa. Pressione "e" e os três eventos de sua classe aparecerão no intellisense. Pressione "c" e as cinco constantes serão listadas.

  • "Eu posso usar em this->vez de m" . Bem, sim, você pode. Mas esse é apenas um prefixo muito mais feio e mais detalhado! Somente ele apresenta um risco muito maior (especialmente em equipes) porque, para o compilador, é opcional e, portanto, seu uso é frequentemente inconsistente. mpor outro lado, é breve, claro, explícito e não opcional, por isso é muito mais difícil cometer erros ao usá-lo.

Jason Williams
fonte
6
Quero dizer que li que o problema com a notação húngara resultou apenas da incompreensão de Simonyi. Ele escreveu um prefixo para indicar o tipo de uma variável em que ele quis dizer "tipo", como em "tipo de coisa", não tipo de dados literal. Mais tarde, os caras plataforma da Microsoft pegou e veio com lpsz ... eo resto é história ...
VoidPointer
19
"s é para estática" soa como a forma "ruim" de húngaro para mim.
jalf
6
@ Mehrdad: Eu não acho que zseja muito útil em uma linguagem como C ++, onde esse tipo de detalhe de implementação de baixo nível deve ser encapsulado em uma classe, mas em C (onde terminação zero é uma distinção importante) eu concordo com você. Na IMO, qualquer esquema que usamos deve ser adaptado conforme necessário para melhor atender às nossas próprias necessidades - Portanto, se a terminação zero afeta o uso da variável, não há nada de errado em declarar "z" um prefixo útil.
Jason Williams
14
The most important case is "p" for pointer (because the usage changes from var. to var-> and you have to be much more careful with pointers.Eu discordo de todo o coração. Se eu usar um ponteiro errado, ele simplesmente não será compilado ( void*pode ser uma exceção para ponteiros duplos). E todo o ->mais .é o suficiente para me dizer que é um ponteiro. Além disso, se você estiver usando o preenchimento automático, seu editor provavelmente terá dicas de ferramentas de declaração, eliminando a necessidade de prefixar informações variáveis. Independentemente, boa resposta.
Thomas Eding
5
Promovido o voto para obter explicações claras, abrangentes e interessantes, no entanto, há pouco aqui sugerindo como isso economiza tempo no C ++ YET, que permanece amplamente não utilizado em muitos outros idiomas.
115

Eu geralmente não uso um prefixo para variáveis ​​de membro.

Eu costumava usar um mprefixo, até que alguém apontou que "C ++ já tem um prefixo padrão para o acesso de membros: this->.

Então é isso que eu uso agora. Ou seja, quando há ambiguidade , adiciono o this->prefixo, mas geralmente não existe ambiguidade e posso me referir diretamente ao nome da variável.

Para mim, esse é o melhor dos dois mundos. Eu tenho um prefixo que posso usar quando preciso e estou livre para deixá-lo de fora sempre que possível.

Obviamente, o contrário óbvio disso é "sim, mas você não pode ver rapidamente se uma variável é ou não um membro da classe".

Para o que eu digo "e daí? Se você precisa saber disso, sua classe provavelmente tem muito estado. Ou a função é muito grande e complicada".

Na prática, descobri que isso funciona extremamente bem. Como um bônus adicional, permite-me promover uma variável local para um membro da classe (ou vice-versa) facilmente, sem ter que renomeá-la.

E o melhor de tudo, é consistente! Não preciso fazer nada de especial nem lembrar de convenções para manter a consistência.


A propósito, você não deve usar sublinhados de destaque para os alunos. Você fica desconfortavelmente próximo dos nomes reservados pela implementação.

O padrão reserva todos os nomes começando com sublinhado duplo ou sublinhado, seguido de letra maiúscula. Ele também reserva todos os nomes começando com um único sublinhado no espaço para nome global .

Portanto, um membro da classe com um sublinhado à esquerda seguido de uma letra minúscula é legal, mas mais cedo ou mais tarde você fará o mesmo com um identificador que começa com maiúscula ou violará uma das regras acima.

Portanto, é mais fácil evitar os sublinhados principais. Use um sublinhado postfix, ou um m_ou apenas mprefixo, se desejar codificar o escopo no nome da variável.

jalf
fonte
"Portanto, um membro da classe com um sublinhado à esquerda seguido de uma letra minúscula é legal, mas mais cedo ou mais tarde você fará o mesmo com um identificador que começa com maiúscula ou violará uma das regras acima". - as variáveis ​​de membro da classe não estão no espaço de nomes global; portanto, um sublinhado à esquerda é seguro, independentemente de ser seguido por uma letra maiúscula ou minúscula.
mbarnett
3
@mbarnett: Não, o sublinhado seguido por maiúscula é reservado em geral , não apenas no espaço de nomes global.
jalf
9
surpreendeu que o voto desta resposta seja menor que o prefixo.
Marson Mao
Eu concordo com esta resposta, basta usar this-> se você precisar especificar que é uma variável membro, ou não, isso também é bom.
David Morton
Além disso, você não precisa documentar sua convenção para fornecer seu código a outras pessoas. Todo mundo entende o que this->significa.
Caduchon 21/09/17
34

Prefiro sublinhados postfix, como:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};
jkeys
fonte
Eu também. Também dou aos acessadores / mutadores o mesmo nome.
4133 Rob
4
Interessante. Parece um pouco feio no começo, mas posso ver como isso pode ser benéfico.
ya23
6
Eu diria que é muito menos feio do que: "mBar" ou "m_bar".
Sydän
6
mas então você tem vector<int> v_;e escrita v_.push_back(5)é muito feio demais
AVIM
4
Esse é o estilo do Google C ++.
Justme0
20

Ultimamente, tenho tendido a preferir o prefixo m_ em vez de não ter nenhum prefixo, o motivo não é tanto o fato de ser importante sinalizar variáveis ​​de membro, mas evita a ambiguidade, digamos que você tenha um código como:

void set_foo(int foo) { foo = foo; }

A causa não funciona, apenas uma é foopermitida. Portanto, suas opções são:

  • this->foo = foo;

    Eu não gosto, pois causa sombreamento de parâmetro, você não pode mais usar g++ -Wshadowavisos, também é mais tempo digitar então m_. Você também ainda encontra conflitos de nomenclatura entre variáveis ​​e funções quando tem umaint foo; e a int foo();.

  • foo = foo_; ou foo = arg_foo;

    Utilizo isso há algum tempo, mas torna as listas de argumentos feias, a documentação não deveria lidar com a desambigüidade de nomes na implementação. Os conflitos de nomeação entre variáveis ​​e funções também existem aqui.

  • m_foo = foo;

    A documentação da API permanece limpa, você não tem ambiguidade entre funções e variáveis-membro e é mais curto para digitar this->. A única desvantagem é que isso torna as estruturas do POD feias, mas como as estruturas do POD não sofrem com a ambiguidade do nome em primeiro lugar, não é necessário usá-lo com elas. Ter um prefixo exclusivo também facilita algumas operações de pesquisa e substituição.

  • foo_ = foo;

    A maioria das vantagens se m_aplica, mas eu a rejeito por razões estéticas, um sublinhado à direita ou à esquerda apenas faz a variável parecer incompleta e desequilibrada. m_apenas parece melhor. O uso m_também é mais extensível, como você pode usar g_para globais e s_estáticos.

PS: A razão pela qual você não vê m_no Python ou no Ruby é porque as duas linguagens aplicam o próprio prefixo, o Ruby usa @para variáveis ​​de membro e o Python exige self..

Grumbel
fonte
1
para ser justo, você perdeu pelo menos 2 outras opções, por exemplo: (a) use nomes completos como fooapenas para membros e, em vez disso, use uma letra ou nomes abreviados para parâmetros ou outros locais / pessoas comuns, comoint f ; ou (b) prefixe os parâmetros ou outros locais com alguma coisa. bom ponto re m_e pods, no entanto; Independentemente, cheguei à preferência de seguir essas duas diretrizes.
underscore_d
12

Ao ler uma função de membro, saber quem "possui" cada variável é absolutamente essencial para entender o significado da variável. Em uma função como esta:

void Foo::bar( int apples )
{
    int bananas = apples + grapes;
    melons = grapes * bananas;
    spuds += melons;
}

... é fácil ver de onde vêm as maçãs e as bananas, mas e as uvas, melões e batatas? Devemos procurar no espaço para nome global? Na declaração de classe? A variável é membro desse objeto ou membro da classe deste objeto? Sem saber a resposta para essas perguntas, você não consegue entender o código. E, em uma função mais longa, mesmo as declarações de variáveis ​​locais como maçãs e bananas podem se perder no shuffle.

Anexar um rótulo consistente para globais, variáveis ​​de membro e variáveis ​​de membro estáticas (talvez g_, m_ e s_ respectivamente) esclarece instantaneamente a situação.

void Foo::bar( int apples )
{
    int bananas = apples + g_grapes;
    m_melons = g_grapes * bananas;
    s_spuds += m_melons;
}

Isso pode levar algum tempo para se acostumar - mas então, o que na programação não? Houve um dia em que até {e} pareciam estranhos para você. E depois que você se acostuma, eles ajudam a entender o código muito mais rapidamente.

(Usar "this->" no lugar de m_ faz sentido, mas é ainda mais demorado e visualmente perturbador. Não a vejo como uma boa alternativa para marcar todos os usos de variáveis-membro.)

Uma possível objeção ao argumento acima seria estender o argumento para tipos. Também pode ser verdade que conhecer o tipo de uma variável "é absolutamente essencial para entender o significado da variável". Se é assim, por que não adicionar um prefixo a cada nome de variável que identifica seu tipo? Com essa lógica, você acaba com a notação húngara. Mas muitas pessoas acham a notação húngara trabalhosa, feia e inútil.

void Foo::bar( int iApples )
{
    int iBananas = iApples + g_fGrapes;
    m_fMelons = g_fGrapes * iBananas;
    s_dSpuds += m_fMelons;
}

húngaro fazconte-nos algo novo sobre o código. Agora entendemos que existem várias conversões implícitas na função Foo :: bar (). O problema com o código agora é que o valor das informações adicionadas pelos prefixos húngaros é pequeno em relação ao custo visual. O sistema do tipo C ++ inclui muitos recursos para ajudar os tipos a trabalharem bem juntos ou para gerar um aviso ou erro do compilador. O compilador nos ajuda a lidar com tipos - não precisamos de notação para isso. Podemos inferir com bastante facilidade que as variáveis ​​em Foo :: bar () provavelmente são numéricas e, se isso é tudo o que sabemos, é bom o suficiente para obter uma compreensão geral da função. Portanto, o valor de conhecer o tipo preciso de cada variável é relativamente baixo. No entanto, a feiúra de uma variável como "s_dSpuds" (ou mesmo apenas "dSpuds") é grande. Assim,

OldPeculier
fonte
Obrigado pela ideia s_. Parece muito útil, e de alguma forma nunca me ocorreu.
Chris Olsen
10

Não sei dizer o quanto isso é generalizado, mas falando pessoalmente, sempre (e sempre) prefixo minhas variáveis ​​de membro com 'm'. Por exemplo:

class Person {
   .... 
   private:
       std::string mName;
};

É a única forma de prefixo que eu uso (sou uma notação muito anti-húngara), mas ela me manteve em boa posição ao longo dos anos. Como um aparte, geralmente detesto o uso de sublinhados em nomes (ou em qualquer outro lugar), mas faço uma exceção para nomes de macro de pré-processador, pois eles geralmente são todos em maiúsculas.


fonte
5
O problema com o uso de m, em vez de m_ (ou _), está na moda atual dos camelos, dificultando a leitura de alguns nomes de variáveis.
223 Martin Beckett
1
@ Neil estou com você. @mgb: Eu odeio nomes começando com '_' É apenas um convite para que as coisas dêem errado no futuro.
Martin Iorque
1
@ Neil: Qual convenção você usa então, se você não usa sublinhados e não usa camelcase?
jalf
2
Meu entendimento era que é camelCase que torna confuso o uso de apenas m para variáveis ​​como 'apData' - ele se torna 'mapData' em vez de 'm_apData'. Eu uso _camelCase para variáveis protegida / Estados privado, porque ele se destaca
Martin Beckett
10
@MartinBeckett: Você deve capitalizar o acenário - você não está fazendo certo de outra maneira. mApData( mprefixo, o nome da variável é apData).
Platinum Azure
8

O principal motivo para um prefixo de membro é distinguir entre uma função de membro local e uma variável de membro com o mesmo nome. Isso é útil se você usar getters com o nome da coisa.

Considerar:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

A variável de membro não pôde ser chamada full_name nesse caso. Você precisa renomear a função de membro para get_full_name () ou decorar a variável de membro de alguma forma.

Lobo
fonte
1
É por isso que prefixo. Eu acho que foo.name()é muito mais legível do que foo.get_name()na minha opinião.
Terrabits 14/07/16
6

Eu não acho que uma sintaxe tenha valor real sobre outra. Tudo se resume, como você mencionou, à uniformidade nos arquivos de origem.

O único ponto em que acho essas regras interessantes é quando preciso de duas coisas nomeadas idênticas, por exemplo:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

Eu o uso para diferenciar os dois. Além disso, quando envolvo chamadas, como na DLL do Windows, o RecvPacket (...) da DLL pode ser agrupado em RecvPacket (...) no meu código. Nessas ocasiões, o uso de um prefixo como "_" pode tornar os dois parecidos, fácil de identificar qual é qual, mas diferente para o compilador

Eric
fonte
6

Algumas respostas se concentram na refatoração, em vez de convenções de nomenclatura, como o caminho para melhorar a legibilidade. Não acho que um possa substituir o outro.

Conheço programadores que se sentem desconfortáveis ​​com o uso de declarações locais; eles preferem colocar todas as declarações no topo de um bloco (como em C), para saber onde encontrá-las. Descobri que, onde o escopo permite, declarar variáveis ​​onde são usadas pela primeira vez diminui o tempo que gasto olhando para trás para encontrar as declarações. (Isso é verdade para mim, mesmo para pequenas funções.) Isso facilita a compreensão do código que estou vendo.

Espero que seja claro o suficiente como isso se relaciona às convenções de nomenclatura de membros: quando os membros são uniformemente prefixados, nunca preciso olhar para trás; Eu sei que a declaração nem será encontrada no arquivo de origem.

Tenho certeza de que não comecei a preferir esses estilos. No entanto, com o tempo, trabalhando em ambientes onde eles eram usados ​​de forma consistente, eu otimizei meu pensamento para tirar proveito deles. Eu acho que é possível que muitas pessoas que atualmente se sentem desconfortáveis ​​com eles também venham a preferi-los, dado o uso consistente.

Dan Breslau
fonte
5

Essas convenções são exatamente isso. A maioria das lojas usa convenções de código para facilitar a legibilidade do código, para que qualquer pessoa possa ver facilmente um pedaço de código e decifrar rapidamente entre itens como membros públicos e privados.

Sr. Will
fonte
"entre coisas como membros públicos e privados" - quão comum isso é realmente? Eu não me lembro de vê-lo, mas, novamente, eu não saio por aí revisando bases de código ou algo assim.
underscore_d
Eu não faço isso na minha própria codificação, mas trabalhei em locais onde tivemos que fazê-lo com base em seus guias de convenções de código. Prefiro não fazê-lo, pois quase todos os IDEs mostram variáveis ​​privadas de uma cor diferente.
Sr. Will
Hmm, acho que isso só acontece em situações diferentes das minhas. Normalmente eu uso classes todos de cujos membros são private/ protectedou POD structs cujas variáveis ​​são public(e geralmente também const). Portanto, nunca preciso me perguntar sobre o nível de acesso de qualquer membro.
underscore_d
5

Outros tentam aplicar usando this-> member sempre que uma variável de membro é usada

Isso geralmente ocorre porque não há prefixo . O compilador precisa de informações suficientes para resolver a variável em questão, seja um nome exclusivo por causa do prefixo ou por meio da thispalavra - chave.

Então, sim, acho que os prefixos ainda são úteis. Eu, por exemplo, preferiria digitar '_' para acessar um membro em vez de 'this->'.

Kent Boogaart
fonte
3
o compilador pode resolvê-lo de qualquer maneira ... variáveis ​​locais ocultam aquelas em escopo mais alto na maioria dos idiomas. É para o benefício (duvidoso) dos humanos que leem o código. Qualquer IDE decente irá destacar moradores / usuários / globals de maneiras diferentes, de modo que não há necessidade para esse tipo de coisa
rmeador
1
Exatamente. Os locais ocultarão os alunos. Considere um construtor que define esses membros. Geralmente, faz sentido nomear os parâmetros da mesma forma que os membros.
Kent Boogaart 04/08/09
6
Por que isso é um cheiro de código? Eu diria que é perfeitamente comum e razoável, especialmente quando se trata de construtores.
Kent Boogaart 04/08/09
3
Um construtor deve (geralmente) definir locais em sua lista de inicialização. E lá, parâmetros não sombra nomes de campos, mas ambos são acessíveis - assim você pode escreverstruct Foo { int x; Foo(int x) : x(x) { ... } };
Pavel Minaev
2
Presumo que o problema com que vem quando você faz Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->eu prefiro chamar de meu variáveis de membro algo útil e, em seguida, dar parâmetros do construtor que correspondem lhes nomes abreviados:Foo(int f) : foo(f) {...}
Steve Jessop
4

Outros idiomas usarão convenções de codificação, apenas tendem a ser diferentes. O C #, por exemplo, provavelmente possui dois estilos diferentes que as pessoas tendem a usar, um dos métodos C ++ (_variable, mVariable ou outro prefixo, como notação húngara), ou o que eu me refiro como o método StyleCop.

private int privateMember;
public int PublicMember;

public int Function(int parameter)
{
  // StyleCop enforces using this. for class members.
  this.privateMember = parameter;
}

No final, torna-se o que as pessoas sabem e o que parece melhor. Pessoalmente, acho que o código é mais legível sem a notação húngara, mas pode ser mais fácil encontrar uma variável com intellisense, por exemplo, se a notação húngara estiver anexada.

No meu exemplo acima, você não precisa de um prefixo m para variáveis-membro porque prefixa seu uso com isso. indica a mesma coisa em um método imposto pelo compilador.

Isso não significa necessariamente que os outros métodos sejam ruins, as pessoas seguem o que funciona.

Will Eddins
fonte
3

Quando você tem um método grande ou blocos de código, é conveniente saber imediatamente se você usa uma variável local ou um membro. é para evitar erros e para uma melhor clareza!

Matthieu
fonte
3
Se você tem um método grande, para uma melhor clareza, divida-o.
Sbi # 04/08
4
Existem muitas razões para não quebrar alguns grandes métodos. Por exemplo, se seu método precisar manter muitos estados locais, você precisará passar muitos parâmetros para seus métodos subordinados, criar novas classes que existam apenas com a finalidade de passar dados entre esses métodos ou declarar os dados do estado como dados de membro da classe pai. Todos eles têm problemas que afetariam a clareza ou a capacidade de manutenção do método, em comparação com um único método longo (especialmente aquele cuja lógica é direta).
213 Steve Broberg
3
@sbi: Diretrizes são exatamente isso; diretrizes, não regras. Às vezes, você precisa de métodos grandes que, logicamente, não se dividem e, às vezes, os nomes de parâmetros se chocam com os membros.
224 Ed Ed S.
Por favor, não torne suas variáveis ​​de membro públicas. Basta usar acessadores. Os parênteses devem informar ao leitor que é uma variável de membro.
jkeys
Observe que existe um aviso no gcc (> = 4.6) para detectar conflito de nomes:-Wshadow
Caduchon
3

IMO, isso é pessoal. Não estou colocando nenhum prefixo. De qualquer forma, se o código é público, acho que deveria ter alguns prefixos, para que seja mais legível.

Muitas vezes, grandes empresas estão usando suas próprias 'regras de desenvolvedor'.
Aliás, o mais engraçado e inteligente que eu vi foi o DRY KISS (não se repita. Mantenha as coisas simples, estúpidas). :-)

Andrejs Cainikovs
fonte
3

Como outros já disseram, a importância é ser coloquial (adaptar os estilos e convenções de nomenclatura à base de código na qual você está escrevendo) e ser consistente.

Durante anos, trabalhei em uma grande base de código que usa tanto a convenção "this->" quanto uma notação de sublinhado postfix para variáveis-membro. Ao longo dos anos, também trabalhei em projetos menores, alguns dos quais não tinham nenhum tipo de convenção para nomear variáveis ​​de membros e outros que tinham convenções diferentes para nomear variáveis ​​de membros. Desses projetos menores, eu sempre achei aqueles que careciam de convenção eram os mais difíceis de entrar rapidamente e entender.

Eu sou muito reticente quanto a nomes. Vou agonizar sobre o nome a ser atribuído a uma classe ou variável, a ponto de, se não conseguir algo que me pareça "bom", optarei por chamá-lo de algo sem sentido e fornecer um comentário descrevendo o que realmente é. é. Dessa forma, pelo menos o nome significa exatamente o que pretendo que signifique - nada mais e nada menos. E, muitas vezes, após usá-lo por um tempo, eu descubro que o nome deve realmente ser e pode voltar e modificar ou refactor adequadamente.

Um último ponto sobre o tópico de um IDE fazendo o trabalho - tudo isso é bom e bom, mas os IDEs geralmente não estão disponíveis em ambientes nos quais realizo o trabalho mais urgente. Às vezes, a única coisa disponível nesse momento é uma cópia do 'vi'. Além disso, já vi muitos casos em que o preenchimento de código IDE propagou estupidez, como ortografia incorreta nos nomes. Portanto, prefiro não ter que confiar em uma muleta IDE.

Chris Cleeland
fonte
3

A idéia original para prefixos em variáveis ​​de membro C ++ era armazenar informações de tipo adicionais que o compilador não conhecia. Assim, por exemplo, você pode ter uma string com um comprimento fixo de caracteres e outra variável e terminada com um '\ 0'. Para o compilador, são ambos char *, mas se você tentar copiar de um para o outro, terá um grande problema. Então, em cima da minha cabeça,

char *aszFred = "Hi I'm a null-terminated string";
char *arrWilma = {'O', 'o', 'p', 's'};

onde "asz" significa que esta variável é "string ascii (terminação zero) e" arr "significa que essa variável é uma matriz de caracteres.

Então a mágica acontece. O compilador ficará perfeitamente satisfeito com esta declaração:

strcpy(arrWilma, aszFred);

Mas você, como humano, pode olhar e dizer "ei, essas variáveis ​​não são realmente do mesmo tipo, eu não posso fazer isso".

Infelizmente, muitos lugares usam padrões como "m_" para variáveis-membro, "i" para números inteiros, não importa como usados, "cp" para ponteiros de caracteres. Em outras palavras, eles estão duplicando o que o compilador sabe e dificultando a leitura do código ao mesmo tempo. Acredito que essa prática perniciosa deve ser proibida por lei e sujeita a penalidades severas.

Finalmente, há dois pontos que devo mencionar:

  • O uso criterioso dos recursos do C ++ permite que o compilador saiba as informações que você tinha que codificar nas variáveis ​​brutas do estilo C. Você pode criar classes que permitirão apenas operações válidas. Isso deve ser feito tanto quanto possível.
  • Se os seus blocos de código são tão longas que você esquecer o tipo de uma variável antes de usá-lo, eles são caminho muito longo. Não use nomes, reorganize.
Al Flanagan
fonte
Prefixos que indicam o tipo ou tipo de variável também são algo que vale a pena discutir, mas eu estava me referindo principalmente a prefixos que indicam se algo é um membro / campo (privado). A notação húngara reversa que você está mencionando pode ser bastante útil quando aplicada de maneira inteligente (como no seu exemplo). Meu exemplo favorito, onde faz sentido, é coordenadas relativas e absolutas. quando você vê absX = relX, pode ver claramente que algo pode estar errado. Você também pode nomear as funções adequadamente: absX = absFromRel (relX, offset);
VoidPointer 04/08/09
Nota: a inicialização do aszFred é questionável (oferecendo acesso não const a uma string literal) e a inicialização do arrWilma nem será compilada. (Você provavelmente pretendia declarar arrWilma como uma matriz, em vez de um ponteiro!) Não haverá problema embora, como você escreveu que é apenas fora do topo da sua cabeça ... :-)
Niels Dekker
Opa, você está absolutamente certo. Crianças, não tente isso em casa. Faça o seguinte: 'const char * aszFred = "Olá, sou uma string terminada em nulo"; char arrWilma [] = {'O', 'o', 'p', 's'}; '
AL Flanagan
3

Nosso projeto sempre usou "its" como prefixo para dados de membros e "the" como prefixo para parâmetros, sem prefixo para locais. É um pouco fofo, mas foi adotado pelos primeiros desenvolvedores do nosso sistema porque eles o viram usado como uma convenção por algumas bibliotecas de fontes comerciais que estávamos usando na época (XVT ou RogueWave - talvez ambos). Então você obteria algo como isto:

void
MyClass::SetName(const RWCString &theName)
{
   itsName = theName;
}

A grande razão que vejo para os prefixos de escopo (e não outros - eu odeio a notação húngara) é que ela evita que você tenha problemas escrevendo códigos nos quais pensa estar se referindo a uma variável, mas na verdade está se referindo a outra variável com o mesmo nome definido no escopo local. Também evita o problema de criar nomes de variáveis ​​para representar o mesmo conceito, mas com escopos diferentes, como no exemplo acima. Nesse caso, você teria que criar um prefixo ou nome diferente para o parâmetro "theName" de qualquer maneira - por que não fazer uma regra consistente que se aplique a todos os lugares.

Apenas usar isso-> não é realmente bom o suficiente - não estamos tão interessados ​​em reduzir a ambiguidade quanto em reduzir erros de codificação, e mascarar nomes com identificadores com escopo local pode ser um problema. É verdade que alguns compiladores podem ter a opção de emitir avisos para casos em que você mascara o nome em um escopo maior, mas esses avisos podem se tornar um incômodo se você estiver trabalhando com um grande conjunto de bibliotecas de terceiros que escolheram nomes para variáveis ​​não utilizadas que ocasionalmente colidem com as suas.

Quanto ao próprio - eu sinceramente acho mais fácil digitar do que os sublinhados (como datilógrafo de toque, evito sublinhados sempre que possível - esticando demais as linhas da página inicial) e acho mais legível do que um sublinhado misterioso.

Steve Broberg
fonte
Esta é a solução mais intuitiva com a curva de aprendizado mais rápida que eu já ouvi. Eu gostaria que as línguas faladas fossem mais flexíveis para lidar com todas elas, para que não precisássemos pensar em novas técnicas para resolver ambiguidades no código.
precisa saber é o seguinte
2

Eu o uso porque o Intellisense do VC ++ não pode dizer quando mostrar membros particulares ao acessar fora da classe. A única indicação é um pequeno símbolo de "cadeado" no ícone do campo na lista do Intellisense. Apenas facilita a identificação de membros privados (campos). Também é um hábito do C # para ser honesto.

class Person {
   std::string m_Name;
public:
   std::string Name() { return m_Name; }
   void SetName(std::string name) { m_Name = name; }
};

int main() {
  Person *p = new Person();
  p->Name(); // valid
  p->m_Name; // invalid, compiler throws error. but intellisense doesn't know this..
  return 1;
}
Zack
fonte
2

Eu acho que, se você precisar de prefixos para distinguir os membros da classe dos parâmetros da função de membro e variáveis ​​locais, a função é muito grande ou as variáveis ​​são nomeadas incorretamente. Se não couber na tela, para que você possa ver facilmente o que é o que, refatorar.

Dado que eles geralmente são declarados longe de onde são usados, acho que as convenções de nomenclatura para constantes globais (e variáveis ​​globais, embora a IMO raramente exija a necessidade de usá-las) fazem sentido. Mas, caso contrário, não vejo muita necessidade.

Dito isto, eu costumava colocar um sublinhado no final de todos os alunos da classe particular. Como todos os meus dados são privados, isso implica que os membros tenham um sublinhado à direita. Normalmente, não faço mais isso em novas bases de código, mas como programador, você trabalha principalmente com código antigo, ainda faço muito isso. Não tenho certeza se minha tolerância a esse hábito vem do fato de que eu costumava fazer isso sempre e ainda o faço regularmente ou se realmente faz mais sentido do que a marcação de variáveis-membro.

sbi
fonte
2
Isso reflete muito meu sentimento sobre esse assunto. O código deve ser legível sem recorrer a prefixos. Talvez não vejamos tanto o uso de prefixos em linguagens mais modernas porque suas comunidades de usuários adotaram a legibilidade um pouco mais do que o que às vezes você vê em C ++. Obviamente, o C ++ pode e deve ser legível. É que muitos C ++ ilegíveis foram escritos ao longo dos anos.
VoidPointer 04/08/09
2

No python, os sublinhados duplos principais são usados ​​para emular membros privados. Para mais detalhes, veja esta resposta

Konstantin Tenzin
fonte
1

É útil diferenciar entre variáveis-membro e variáveis ​​locais devido ao gerenciamento de memória. Em termos gerais, as variáveis ​​de membro alocadas em heap devem ser destruídas no destruidor, enquanto as variáveis ​​locais alocadas em heap devem ser destruídas nesse escopo. A aplicação de uma convenção de nomenclatura a variáveis-membro facilita o gerenciamento correto da memória.

frankster
fonte
como assim? O destruidor não tem acesso a variáveis ​​locais declaradas em outras funções; portanto, não há espaço para confusão lá. Além disso, variáveis ​​locais alocadas em heap não deveriam existir . E as variáveis ​​de membro alocadas em heap devem existir apenas dentro das classes RAII, basicamente.
jalf
"variáveis ​​locais alocadas em heap não deveriam existir" é um pouco forte. Mas se / quando você os usar, é super importante garantir que eles sejam desalocados corretamente, portanto, uma convenção de nomenclatura disciplinada para variáveis ​​de membro versus variáveis ​​locais ajuda imensamente a garantir isso.
frankster
1

O código completo recomenda m_varname para variáveis ​​de membro.

Embora eu nunca tenha achado a notação m_ útil, eu daria peso à opinião de McConnell na construção de um padrão.

Paul Nathan
fonte
2
Não, a menos que ele explique por que o sublinhado. Eu sou um grande fã do livro "Rapid Development", que eu recomendei aqui várias vezes, mas muito menos do "Code Complete" (que eu admito que não li desde que foi lançado).
1

Eu quase nunca uso prefixos na frente dos meus nomes de variáveis. Se você estiver usando um IDE decente o suficiente, poderá refatorar e encontrar referências facilmente. Eu uso nomes muito claros e não tenho medo de ter nomes de variáveis ​​longos. Nunca tive problemas com o escopo dessa filosofia.

A única vez que eu uso um prefixo seria na linha de assinatura. Vou prefixar os parâmetros para um método com _ para que eu possa programar defensivamente em torno deles.

James
fonte
1

Você nunca deve precisar desse prefixo. Se esse prefixo oferecer alguma vantagem, seu estilo de codificação geralmente precisa ser corrigido, e não é o prefixo que impede que seu código seja claro. Os nomes de variáveis ​​incorretos típicos incluem "outro" ou "2". Você não corrige isso exigindo que seja mais, consertando-o fazendo com que o desenvolvedor pense sobre o que essa variável está fazendo lá no contexto dessa função. Talvez ele quis dizer remoteSide, ou newValue, ou secondTestListener ou algo nesse escopo.

É um anacronismo eficaz que ainda é propagado longe demais. Pare de prefixar suas variáveis ​​e dê a elas nomes próprios cuja clareza reflita quanto tempo eles são usados. Até 5 linhas você pode chamar de "i" sem confusão; além de 50 linhas, você precisa de um nome bastante longo.

dascandy
fonte
1

Eu gosto de nomes de variáveis ​​para dar apenas um significado aos valores que eles contêm e deixar como eles são declarados / implementados fora do nome. Eu quero saber o que o valor significa, ponto final. Talvez eu tenha feito mais do que uma quantidade média de refatoração, mas acho que incorporar como algo é implementado no nome torna a refatoração mais tediosa do que precisa. Os prefixos que indicam onde ou como os membros do objeto são declarados são específicos da implementação.

color = Red;

Na maioria das vezes, eu não me importo se Red é um enum, uma estrutura ou qualquer outra coisa, e se a função é tão grande que não consigo me lembrar se a cor foi declarada localmente ou é um membro, provavelmente é hora de quebrar a função em unidades lógicas menores.

Se a sua complexidade ciclomática é tão grande que você não pode acompanhar o que está acontecendo no código sem pistas específicas da implementação incorporadas nos nomes das coisas, provavelmente você precisará reduzir a complexidade da sua função / método.

Principalmente, eu apenas uso 'this' em construtores e inicializadores.

ChrisG65
fonte
0

Uso m_ para variáveis-membro apenas para aproveitar o Intellisense e a funcionalidade IDE relacionada. Ao codificar a implementação de uma classe, posso digitar m_ e ver a caixa de combinação com todos os m_ membros agrupados.

Mas eu poderia viver sem m sem problemas, é claro. É apenas o meu estilo de trabalho.

Hernán
fonte
Você também pode digitarthis->
Toast
0

De acordo com as NORMAS DE CODIFICAÇÃO DO VEÍCULO AÉREO COMUM STRIKE FIGHTER AIR (dezembro de 2005):

Regra AV 67

Dados públicos e protegidos devem ser usados ​​apenas em estruturas - não em classes. Justificativa: Uma classe é capaz de manter sua invariante controlando o acesso aos seus dados. No entanto, uma classe não pode controlar o acesso a seus membros se eles não forem privados. Portanto, todos os dados em uma classe devem ser privados.

Assim, o prefixo "m" se torna inútil, pois todos os dados devem ser privados.

Mas é um bom hábito usar o prefixo p antes de um ponteiro, pois é uma variável perigosa.

Pierre-Louis Deschamps
fonte
0

Muitas dessas convenções são de uma época sem editores sofisticados. Eu recomendaria o uso de um IDE adequado que permita colorir todos os tipos de variáveis. A cor é muito mais fácil de detectar do que qualquer prefixo.

Se você precisar obter ainda mais detalhes sobre uma variável, qualquer IDE moderno poderá mostrar isso movendo o cursor ou cursor sobre ele. E se você usar uma variável de maneira errada (por exemplo, um ponteiro com o operador.), Você receberá um erro de qualquer maneira.

Elias Mueller
fonte