¿Por qué usar prefijos en variables miembro en clases de C ++?

150

Una gran cantidad de código C ++ utiliza convenciones sintácticas para marcar las variables miembro. Ejemplos comunes incluyen

  • m_ memberName para miembros públicos (donde se usan miembros públicos)
  • _ memberName para miembros privados o todos los miembros

Otros intentan imponer el uso de este-> miembro cada vez que se usa una variable miembro.

En mi experiencia, la mayoría de las bases de código más grandes fallan al aplicar tales reglas de manera consistente.

En otros idiomas, estas convenciones están mucho menos extendidas. Lo veo solo ocasionalmente en código Java o C #. Creo que nunca lo he visto en código Ruby o Python. Por lo tanto, parece haber una tendencia con lenguajes más modernos para no usar marcado especial para las variables miembro.

¿Sigue siendo útil esta convención hoy en día en C ++ o es solo un anacronismo? Especialmente porque se usa de manera tan inconsistente en las bibliotecas. ¿No se muestran los otros idiomas que uno puede prescindir de los prefijos de miembro?

VoidPointer
fuente
15
Lo prefiero; En bases de código complejas puede ser importante saber qué variables son locales y cuáles no. Yo generalmente uso el prefijo sobre obligando this-> lo que me parece ser un montón de escribir adicional y opcional (denominación mientras que le obligará a hacerlo)
Joe
55
Nunca lo has visto en Ruby debido a @ for atributo, y el idioma de generar accesores en lugar de usar atributos directamente.
Steve Jessop el
66
De acuerdo con PEP 8 , las variables de miembros no públicos deben ir precedidas de un guión bajo en Python (ejemplo:) self._something = 1.
Nathan Osman
2
¿No debería usarse el resaltado de sintaxis del editor para identificarlos?
también
2
Usted ha visto el equivalente this->memberen el código Python. En Python, normalmente sería self.membery no es solo una convención, sino que es requerido por el lenguaje.
matec

Respuestas:

48

Debes tener cuidado al usar un guión bajo destacado. Un guión bajo antes de una letra mayúscula en una palabra está reservado. Por ejemplo:

_Foo

_L

son todas palabras reservadas mientras

_foo

_l

no son. Hay otras situaciones en las que los guiones bajos antes de las letras minúsculas no están permitidos. En mi caso específico, encontré que _L estaba reservado para Visual C ++ 2005 y el choque creó algunos resultados inesperados.

Estoy cerca de lo útil que es marcar las variables locales.

Aquí hay un enlace sobre qué identificadores están reservados: ¿Cuáles son las reglas sobre el uso de un guión bajo en un identificador de C ++?

Juan
fuente
44
En realidad, tanto _foo como _l están reservados en el ámbito del espacio de nombres.
13
Pero están bien como nombres de variables miembro. No prefijo guiones bajos, porque las reglas son demasiado confusas y me quemé en el pasado.
Juan
11
Estas no son palabras reservadas. Son nombres reservados. Si fueran palabras reservadas, no podría usarlas en absoluto. Debido a que son nombres reservados, puede usarlos, pero bajo su propio riesgo.
TonyK
235

Estoy totalmente a favor de los prefijos bien hechos .

Creo que la notación húngara (Sistema) es responsable de la mayor parte del "mal rap" que obtienen los prefijos.

Esta notación es en gran medida inútil en lenguajes fuertemente tipados, por ejemplo, en C ++ "lpsz" para decirle que su cadena es un puntero largo a una cadena terminada en nulo, cuando: la arquitectura segmentada es historia antigua, las cadenas C ++ son, por convención común, punteros a terminación nula ¡matrices de caracteres, y no es realmente tan difícil saber que "customerName" es una cadena!

Sin embargo, sí uso prefijos para especificar el uso de una variable (esencialmente "Aplicaciones húngaras", aunque prefiero evitar el término húngaro debido a que tiene una asociación mala e injusta con el sistema húngaro), y esto es muy útil para ahorrar tiempo y enfoque de reducción de errores .

Yo suelo:

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

Cuando deseo aclarar el tipo , uso sufijos estándar (por ejemplo, Lista, ComboBox, etc.).

Esto hace que el programador sea consciente del uso de la variable cada vez que la ve / usa. Podría decirse que el caso más importante es "p" para el puntero (porque el uso cambia de var. A var-> y debe ser mucho más cuidadoso con los punteros: NULL, aritmética de punteros, etc.), pero todos los demás son muy útiles.

Por ejemplo, puede usar el mismo nombre de variable de múltiples maneras en una sola función: (aquí un ejemplo de C ++, pero se aplica igualmente a muchos lenguajes)

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

Puedes ver aquí:

  • No hay confusión entre miembro y parámetro
  • No hay confusión entre índice / iterador y elementos
  • Uso de un conjunto de variables claramente relacionadas (lista de elementos, puntero e índice) que evitan las muchas dificultades de los nombres genéricos (vagos) como "cuenta", "índice".
  • Los prefijos reducen la escritura (más corta y funcionan mejor con autocompletado) que alternativas como "itemIndex" y "itemPtr"

Otro gran punto de los iteradores de "iName" es que nunca indexo una matriz con el índice incorrecto, y si copio un ciclo dentro de otro ciclo no tengo que refactorizar una de las variables de índice del ciclo.

Compare este ejemplo irrealistamente simple:

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

(que es difícil de leer y a menudo conduce al uso de "i" donde se pretendía "j")

con:

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

(que es mucho más legible y elimina toda confusión sobre la indexación. Con el autocompletado en IDE modernos, esto también es rápido y fácil de escribir)

El siguiente beneficio es que los fragmentos de código no requieren ningún contexto para ser entendido. Puedo copiar dos líneas de código en un correo electrónico o un documento, y cualquiera que lea ese fragmento puede notar la diferencia entre todos los miembros, constantes, punteros, índices, etc. No tengo que agregar "oh, y tenga cuidado porque 'data' es un puntero a un puntero ", porque se llama 'ppData'.

Y por la misma razón, no tengo que mover mis ojos de una línea de código para entenderlo. No tengo que buscar a través del código para encontrar si 'datos' son locales, parámetros, miembros o constantes. No tengo que mover mi mano hacia el mouse para poder pasar el puntero sobre 'datos' y luego esperar a que aparezca una información sobre herramientas (que a veces nunca aparece). Por lo tanto, los programadores pueden leer y comprender el código significativamente más rápido, porque no pierden el tiempo buscando arriba y abajo o esperando.

(Si no crees que pierdes el tiempo buscando arriba y abajo para resolver las cosas, encuentra un código que escribiste hace un año y que no has visto desde entonces. Abre el archivo y salta a la mitad sin leerlo. Mira cómo ahora puedes leer desde este punto antes de que no sepas si algo es miembro, parámetro o local. Ahora salta a otra ubicación aleatoria ... Esto es lo que todos hacemos durante todo el día cuando estamos pasando el código de otra persona. o tratando de entender cómo llamar a su función)

El prefijo 'm' también evita la notación "this->" fea y prolija (IMHO) y la inconsistencia que garantiza (incluso si tiene cuidado, generalmente terminará con una mezcla de 'this-> data' y 'datos' en la misma clase, porque nada impone una ortografía coherente del nombre).

'esta' notación está destinada a resolver la ambigüedad , pero ¿por qué alguien escribiría deliberadamente código que puede ser ambiguo? La ambigüedad será conducir a un error, tarde o temprano. Y en algunos idiomas 'esto' no se puede usar para miembros estáticos, por lo que debe introducir 'casos especiales' en su estilo de codificación. Prefiero tener una sola regla de codificación simple que se aplique en todas partes: explícita, inequívoca y coherente.

El último beneficio importante es con Intellisense y autocompletado. Intente utilizar Intellisense en un formulario de Windows para encontrar un evento; debe desplazarse por cientos de métodos misteriosos de clase base a los que nunca necesitará llamar para encontrar los eventos. Pero si cada evento tuviera un prefijo "e", aparecerían automáticamente en un grupo bajo "e". Por lo tanto, el prefijo funciona para agrupar los miembros, concursos, eventos, etc. en la lista de intellisense, lo que hace que sea mucho más rápido y fácil encontrar los nombres que desea. (Por lo general, un método puede tener alrededor de 20-50 valores (locales, parámetros, miembros, concursos, eventos) que son accesibles en su alcance. Pero después de escribir el prefijo (quiero usar un índice ahora, entonces escribo 'i. .. '), se me presentan solo 2-5 opciones de autocompletar.

Soy un programador perezoso, y la convención anterior me ahorra mucho trabajo. Puedo codificar más rápido y cometo muchos menos errores porque sé cómo se deben usar todas las variables.


Argumentos en contra

Entonces, ¿cuáles son los contras? Los argumentos típicos contra los prefijos son:

  • "Los esquemas de prefijos son malos / malos" . Estoy de acuerdo en que "m_lpsz" y sus personajes están mal pensados ​​y son completamente inútiles. Es por eso que aconsejaría usar una notación bien diseñada diseñada para cumplir con sus requisitos, en lugar de copiar algo que sea inapropiado para su contexto. (Use la herramienta adecuada para el trabajo).

  • "Si cambio el uso de algo, tengo que cambiarle el nombre" . Sí, por supuesto que sí, de eso se trata la refactorización, y por qué los IDE tienen herramientas de refactorización para hacer este trabajo de manera rápida y sin dolor. Incluso sin prefijos, cambiar el uso de una variable casi con certeza significa que su nombre debe cambiarse.

  • "Los prefijos solo me confunden" . Al igual que todas las herramientas hasta que aprenda a usarlas. Una vez que su cerebro se haya acostumbrado a los patrones de nomenclatura, filtrará la información automáticamente y realmente no le importará que los prefijos ya estén allí. Pero tiene que usar un esquema como este sólidamente durante una semana o dos antes de que realmente se vuelva "fluido". Y es entonces cuando muchas personas miran el código antiguo y comienzan a preguntarse cómo se las arreglaron sin un buen esquema de prefijos.

  • "Solo puedo mirar el código para resolver esto" . Sí, pero no necesita perder el tiempo buscando en otra parte del código o recordando cada pequeño detalle cuando la respuesta está justo en el punto en que su ojo ya está enfocado.

  • (Parte de) esa información se puede encontrar simplemente esperando que aparezca una información sobre herramientas en mi variable . Si. Cuando sea compatible, para algunos tipos de prefijos, cuando su código se compila limpiamente, después de una espera, puede leer una descripción y encontrar la información que el prefijo habría transmitido instantáneamente. Siento que el prefijo es un enfoque más simple, más confiable y más eficiente.

  • "Es más escribir" . De Verdad? ¿Un personaje entero más? O lo es: con las herramientas de autocompletado IDE, a menudo reducirá la escritura, porque cada carácter de prefijo reduce significativamente el espacio de búsqueda. Presione "e" y los tres eventos en su clase aparecerán en intellisense. Presione "c" y las cinco constantes se enumeran.

  • "Puedo usar en this->lugar de m" . Bueno, si puedes. ¡Pero ese es un prefijo mucho más feo y más detallado! Solo conlleva un riesgo mucho mayor (especialmente en equipos) porque para el compilador es opcional y, por lo tanto, su uso es con frecuencia inconsistente. mpor otro lado es breve, claro, explícito y no opcional, por lo que es mucho más difícil cometer errores al usarlo.

Jason Williams
fuente
66
Me refiero a haber leído que el problema con la notación húngara se debió a la incomprensión de Simonyi. Escribió un prefijo que debería usarse para indicar el tipo de una variable donde quería decir "tipo" como en "tipo de cosa", no tipo de datos literal. Más tarde, los chicos de la plataforma de Microsoft lo recogió y se acercó con lpsz ... y el resto es historia ...
VoidPointer
19
"s is for static" me suena bastante a la forma "mala" del húngaro.
jalf
66
@Mehrdad: No creo que zsea ​​muy útil en un lenguaje como C ++ donde ese tipo de detalle de implementación de bajo nivel debería encapsularse en una clase, pero en C (donde la terminación cero es una distinción importante) estoy de acuerdo con usted. OMI, cualquier esquema que usemos debe adaptarse según sea necesario para satisfacer nuestras propias necesidades. Por lo tanto, si la terminación cero afecta el uso de la variable, no hay nada de malo en declarar "z" como un prefijo útil.
Jason Williams el
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.Estoy totalmente en desacuerdo. Si uso mal un puntero, simplemente no se compilará ( void*aunque puede ser una excepción para los punteros dobles). Y todo lo ->demás .es suficiente para decirme que es un puntero. Además, si está utilizando autocompletar, su editor probablemente tenga información sobre herramientas de declaración, lo que elimina la necesidad de prefijar información variable. De todos modos, buena respuesta.
Thomas Eding
55
Votaron por las explicaciones claras, completas e interesantes, sin embargo, aquí hay poco que sugiera cómo esto ahorra tiempo en C ++. Sin embargo, en muchos otros lenguajes sigue sin utilizarse.
115

Generalmente no uso un prefijo para las variables miembro.

Solía usar un mprefijo, hasta que alguien señalado que "C ++ ya tiene un prefijo estándar para el acceso de miembros: this->.

Entonces eso es lo que uso ahora. Es decir, cuando hay ambigüedad , agrego el this->prefijo, pero generalmente no existe ambigüedad, y puedo referirme directamente al nombre de la variable.

Para mí, eso es lo mejor de ambos mundos. Tengo un prefijo que puedo usar cuando lo necesito, y soy libre de omitirlo siempre que sea posible.

Por supuesto, el contador obvio de esto es "sí, pero no se puede ver de un vistazo si una variable es un miembro de la clase o no".

A lo que digo "¿y qué? Si necesitas saber eso, tu clase probablemente tiene demasiado estado. O la función es demasiado grande y complicada".

En la práctica, he descubierto que esto funciona extremadamente bien. Como beneficio adicional, me permite promocionar una variable local a un miembro de la clase (o al revés) fácilmente, sin tener que cambiarle el nombre.

Y lo mejor de todo, ¡es consistente! No tengo que hacer nada especial ni recordar ninguna convención para mantener la coherencia.


Por cierto, no debes usar guiones bajos para los miembros de tu clase. Te acercas incómodamente a los nombres reservados por la implementación.

El estándar reserva todos los nombres que comienzan con un guión bajo o guión bajo seguido de mayúscula. También reserva todos los nombres que comienzan con un solo guión bajo en el espacio de nombres global .

Por lo tanto, un miembro de la clase con un guión bajo seguido de una letra minúscula es legal, pero tarde o temprano harás lo mismo con un identificador que comience con mayúscula o, de lo contrario, romperás una de las reglas anteriores.

Por lo tanto, es más fácil evitar los guiones bajos. Utilice un guión bajo de prefijo , o un prefijo m_o simplemente msi desea codificar el alcance en el nombre de la variable.

jalf
fuente
"Por lo tanto, un miembro de la clase con un guión bajo seguido de una letra minúscula es legal, pero tarde o temprano harás lo mismo con un identificador que comience con mayúscula, o de lo contrario romperás una de las reglas anteriores". - las variables de miembros de clase no están en el espacio de nombres global, por lo que un guión bajo es seguro, independientemente de si va seguido de una letra mayúscula o minúscula.
mbarnett
3
@mbarnett: No, el guión bajo seguido de mayúsculas está reservado en general , no solo en el espacio de nombres global.
jalf
9
Me sorprende que el voto de esta respuesta sea menor que el prefijo.
Marson Mao
Estoy de acuerdo con esta respuesta, solo use this->si necesita especificar que es una variable miembro, o no, eso también es bueno.
David Morton
Además, no tiene que documentar su convención para dar su código a otras personas. Todos entienden lo que this->significa.
Caduchon
34

Prefiero los subrayados de postfix, como tales:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};
jkeys
fuente
Yo también. También le doy a los accesores / mutadores el mismo nombre.
Rob
44
Interesante. Parece un poco feo al principio, pero puedo ver cómo puede ser beneficioso.
ya23
66
Yo diría que es mucho menos feo que: "mBar" o "m_bar".
sydan
66
pero luego tienes vector<int> v_;y escribir v_.push_back(5)es bastante feo también
avim
44
Ese es el estilo de Google C ++.
Justme0
20

Últimamente he estado tendiendo a preferir el prefijo m_ en lugar de no tener ningún prefijo, las razones no son tanto importantes como para marcar las variables miembro, sino que evitan la ambigüedad, digamos que tiene un código como:

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

La causa no funciona, solo se foopermite una . Entonces sus opciones son:

  • this->foo = foo;

    No me gusta, ya que causa sombreado de parámetros, ya no puede usar g++ -Wshadowadvertencias, también es más largo escribir entonces m_. También te encuentras con conflictos de nombres entre variables y funciones cuando tienes ay int foo;a int foo();.

  • foo = foo_; o foo = arg_foo;

    Lo he estado usando durante un tiempo, pero hace que las listas de argumentos sean feas, la documentación no debería tener que ver con la desambiguación del nombre en la implementación. Los conflictos de nombres entre variables y funciones también existen aquí.

  • m_foo = foo;

    La documentación de API se mantiene limpia, no obtienes ambigüedad entre las funciones y las variables miembro y es más corto de escribir en ese momento this->. La única desventaja es que hace que las estructuras POD sean feas, pero como las estructuras POD no sufren la ambigüedad del nombre en primer lugar, no es necesario usarlo con ellas. Tener un prefijo único también facilita algunas operaciones de búsqueda y reemplazo.

  • foo_ = foo;

    La mayoría de las ventajas de m_aplicar, pero la rechazo por razones estéticas, un guión bajo o de cola solo hace que la variable se vea incompleta y desequilibrada. m_solo se ve mejor. El uso m_también es más extensible, ya que puede usarlo g_para globales y s_para estadísticas.

PD: La razón por la que no se ve m_en Python o Ruby es porque ambos idiomas imponen su propio prefijo, Ruby usa @para las variables miembro y Python lo requiere self..

Grumbel
fuente
1
para ser justos, se perdió al menos otras 2 opciones, por ejemplo (a) usar nombres completos como foosolo para miembros y en su lugar usar nombres simples o cortos para parámetros u otros locales / descartables, como int f; o (b) prefija los parámetros u otros locales con algo. buen punto re m_y vainas, sin embargo; Independientemente, he llegado a preferir seguir ambas pautas, en su mayor parte.
underscore_d
12

Al leer una función miembro, saber quién "posee" cada variable es absolutamente esencial para comprender el significado de la variable. En una función como esta:

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

... es bastante fácil ver de dónde provienen las manzanas y los plátanos, pero ¿qué pasa con las uvas, melones y papas? ¿Deberíamos buscar en el espacio de nombres global? En la declaración de clase? ¿Es la variable un miembro de este objeto o un miembro de la clase de este objeto? Sin saber la respuesta a estas preguntas, no puede entender el código. Y en una función más larga, incluso las declaraciones de variables locales como manzanas y plátanos pueden perderse en la confusión.

Al anteponer una etiqueta consistente para globales, variables miembro y variables miembro estáticas (quizás g_, m_ y s_ respectivamente) se aclara la situación al instante.

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

Al principio, esto puede llevar un tiempo acostumbrarse, pero luego, ¿qué no tiene la programación? Hubo un día en que incluso {y} te parecían extraños. Y una vez que te acostumbras a ellos, te ayudan a entender el código mucho más rápido.

(Usar "this->" en lugar de m_ tiene sentido, pero es aún más largo y visualmente perjudicial. No lo veo como una buena alternativa para marcar todos los usos de las variables miembro).

Una posible objeción al argumento anterior sería extender el argumento a los tipos. También podría ser cierto que conocer el tipo de variable "es absolutamente esencial para comprender el significado de la variable". Si es así, ¿por qué no agregar un prefijo a cada nombre de variable que identifica su tipo? Con esa lógica, terminas con la notación húngara. Pero mucha gente encuentra la notación húngara laboriosa, fea y poco útil.

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

Húngaro haceCuéntanos algo nuevo sobre el código. Ahora entendemos que hay varios modelos implícitos en la función Foo :: bar (). El problema con el código ahora es que el valor de la información agregada por los prefijos húngaros es pequeño en relación con el costo visual. El sistema de tipos C ++ incluye muchas características para ayudar a los tipos a funcionar bien juntos o para generar una advertencia o error del compilador. El compilador nos ayuda a lidiar con los tipos; no necesitamos notación para hacerlo. Podemos inferir fácilmente que las variables en Foo :: bar () son probablemente numéricas, y si eso es todo lo que sabemos, es lo suficientemente bueno para obtener una comprensión general de la función. Por lo tanto, el valor de conocer el tipo preciso de cada variable es relativamente bajo. Sin embargo, la fealdad de una variable como "s_dSpuds" (o incluso "dSpuds") es excelente. Entonces,

OldPeculier
fuente
Gracias por la idea s_. Parece muy útil, y de alguna manera nunca se me había ocurrido.
Chris Olsen
10

No puedo decir qué tan amplio es, pero hablando personalmente, siempre (y siempre he) prefijado mis variables miembro con 'm'. P.ej:

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

Es la única forma de prefijo que uso (soy una notación muy antihúngara) pero me ha sido de gran utilidad a lo largo de los años. Como comentario aparte, generalmente detesto el uso de guiones bajos en los nombres (o en cualquier otro lugar), pero hago una excepción para los nombres de macro de preprocesador, ya que generalmente son mayúsculas.


fuente
55
El problema con el uso de m, en lugar de m_ (o _) es con la moda actual para el caso de camello, lo que dificulta la lectura de algunos nombres de variables.
Martin Beckett el
1
@Neil, estoy contigo. @mgb: Odio los nombres que comienzan con '_' Es solo una invitación a que las cosas salgan mal en el futuro.
Martin York el
1
@Neil: ¿Qué convención usas entonces, si no usas guiones bajos y no usas camelcase?
jalf
2
Comprendí que es camelCase lo que hace que usar solo m para variables como 'apData' sea confuso: se convierte en 'mapData' en lugar de 'm_apData'. Uso _camelCase para variables de miembros protegidos / privados porque se destaca
Martin Beckett
10
@ MartinBeckett: Deberías capitalizar aen ese escenario; de lo contrario, no lo estás haciendo bien. mApData( mprefijo, entonces el nombre de la variable es apData).
Platinum Azure
8

La razón principal de un prefijo de miembro es distinguir entre una función miembro local y una variable miembro con el mismo nombre. Esto es útil si usa getters con el nombre de la cosa.

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_;
};

La variable miembro no podría llamarse full_name en este caso. Debe cambiar el nombre de la función miembro a get_full_name () o decorar la variable miembro de alguna manera.

Lobo
fuente
1
Esta es la razón por la que prefijo. Creo que foo.name()es mucho más legible que foo.get_name()en mi opinión.
Terrabits
6

No creo que una sintaxis tenga un valor real sobre otra. Todo se reduce, como usted mencionó, a la uniformidad en los archivos de origen.

El único punto donde encuentro interesantes tales reglas es cuando necesito 2 cosas nombradas de manera idéntica, por ejemplo:

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

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

Lo uso para diferenciar los dos. También cuando envuelvo llamadas, como desde Windows Dll, RecvPacket (...) del Dll podría estar envuelto en RecvPacket (...) en mi código. En estas ocasiones en particular, usar un prefijo como "_" podría hacer que los dos se parezcan, sean fáciles de identificar cuál es cuál, pero diferentes para el compilador

Eric
fuente
6

Algunas respuestas se centran en refactorizar, en lugar de nombrar convenciones, como la forma de mejorar la legibilidad. No siento que uno pueda reemplazar al otro.

Conozco programadores que se sienten incómodos con el uso de declaraciones locales; prefieren colocar todas las declaraciones en la parte superior de un bloque (como en C), para que sepan dónde encontrarlas. Descubrí que, cuando el alcance lo permite, declarar variables donde se usan por primera vez disminuye el tiempo que paso mirando hacia atrás para encontrar las declaraciones. (Esto es cierto para mí incluso para funciones pequeñas). Eso me facilita la comprensión del código que estoy viendo.

Espero que quede bastante claro cómo se relaciona esto con las convenciones de nomenclatura de miembros: cuando los miembros tienen un prefijo uniforme, nunca tengo que mirar atrás; Sé que la declaración ni siquiera se encontrará en el archivo fuente.

Estoy seguro de que no comencé a preferir estos estilos. Sin embargo, con el tiempo, trabajando en entornos donde se usaban constantemente, optimicé mi pensamiento para aprovecharlos. Creo que es posible que muchas personas que actualmente se sienten incómodas con ellos también los prefieran, dado el uso constante.

Dan Breslau
fuente
5

Esas convenciones son solo eso. La mayoría de las tiendas usan convenciones de código para facilitar la legibilidad del código para que cualquiera pueda ver fácilmente un fragmento de código y descifrar rápidamente entre elementos como los miembros públicos y privados.

Señor will
fuente
"entre cosas como miembros públicos y privados": ¿qué tan común es esto realmente? No recuerdo haberlo visto, pero, una vez más, no reviso las bases de código ni nada.
underscore_d
No lo hago en mi propia codificación, pero he trabajado en lugares donde tuvimos que hacerlo según sus guías de convenciones de código. Prefiero no hacerlo, ya que casi todos los IDE mostrarán variables privadas de un color diferente.
Sr. Will
Hmm, supongo que solo sucede en situaciones diferentes a las mías. Normalmente uso cualquiera classde cuyos miembros son private/ protectedo POD structs cuyas variables son public(y a menudo también const). Por lo tanto, nunca necesito preguntarme sobre el nivel de acceso de un miembro determinado.
underscore_d
5

Otros intentan imponer el uso de este-> miembro cada vez que se usa una variable miembro

Eso es generalmente porque no hay prefijo . El compilador necesita suficiente información para resolver la variable en cuestión, ya sea un nombre único debido al prefijo o mediante la thispalabra clave.

Entonces, sí, creo que los prefijos siguen siendo útiles. Yo, por mi parte, preferiría escribir '_' para acceder a un miembro en lugar de 'this->'.

Kent Boogaart
fuente
3
el compilador puede resolverlo de todos modos ... las variables locales ocultarán las de mayor alcance en la mayoría de los idiomas. Es para el beneficio (dudoso) de los humanos que leen el código. Cualquier IDE decente destacará a los lugareños / miembros / globales de diferentes maneras, por lo que no hay necesidad de este tipo de cosas
Rmeador
1
Exactamente. Los locales esconderán a los miembros de la clase. Considere un constructor que establece estos miembros. Por lo general, tiene sentido nombrar los parámetros de la misma manera que los miembros.
Kent Boogaart el
66
¿Por qué es un olor a código? Diría que es perfectamente común y razonable, especialmente cuando se trata de constructores.
Kent Boogaart el
3
Un constructor debe (generalmente) establecer locales en su lista de inicialización. Y allí, los parámetros no sombrean los nombres de los campos, pero ambos son accesibles, por lo que puede escribirstruct Foo { int x; Foo(int x) : x(x) { ... } };
Pavel Minaev,
2
Supongo que el problema surge cuando lo hace Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->. Prefiero llamar a mis variables miembro algo útil y luego darles parámetros de constructor que coincidan con los nombres abreviados:Foo(int f) : foo(f) {...}
Steve Jessop,
4

Otros lenguajes usarán convenciones de codificación, solo tienden a ser diferentes. C #, por ejemplo, tiene probablemente dos estilos diferentes que las personas tienden a usar, ya sea uno de los métodos de C ++ (_variable, mVariable u otro prefijo como la notación húngara), o lo que yo llamo el método StyleCop.

private int privateMember;
public int PublicMember;

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

Al final, se convierte en lo que la gente sabe y lo que se ve mejor. Personalmente, creo que el código es más legible sin la notación húngara, pero puede ser más fácil encontrar una variable con intellisense, por ejemplo, si se adjunta la notación húngara.

En mi ejemplo anterior, no necesitas un prefijo m para las variables miembro porque prefijas tu uso con esto. indica lo mismo en un método forzado por el compilador.

Esto no significa necesariamente que los otros métodos sean malos, las personas se adhieren a lo que funciona.

Will Eddins
fuente
3

Cuando tiene un método grande o bloques de código, es conveniente saber de inmediato si utiliza una variable local o un miembro. ¡Es para evitar errores y para una mejor claridad!

Matthieu
fuente
3
Si tiene un método grande, para una mejor claridad, desglose.
sbi
44
Hay muchas razones para no analizar algunos métodos importantes. Por ejemplo, si su método necesita mantener una gran cantidad de estado local, debe pasar muchos parámetros a sus métodos subordinados, crear nuevas clases que existan únicamente con el fin de pasar datos entre estos métodos o declarar los datos de estado como datos de miembros de la clase padre. Todos estos tienen problemas que afectarían la claridad o facilidad de mantenimiento del método, en comparación con un único método de larga duración (especialmente uno cuya lógica es sencilla).
Steve Broberg el
3
@sbi: las pautas son solo eso; pautas, no reglas. A veces necesita métodos grandes que no se prestan lógicamente a separarse, y a veces los nombres de los parámetros chocan con los miembros.
Ed S.
No haga públicas sus variables de miembro. Solo usa los accesorios. Los paréntesis deben decirle al lector que es una variable miembro.
jkeys
Tenga en cuenta que hay una advertencia en gcc (> = 4.6) para detectar el choque de nombres:-Wshadow
Caduchon
3

OMI, esto es personal. No estoy poniendo ningún prefijo en absoluto. De todos modos, si se pretende que el código sea público, creo que debería tener algunos prefijos, por lo que puede ser más legible.

A menudo, las grandes empresas utilizan sus propias "reglas de desarrollo".
Por cierto, el más divertido pero más inteligente que vi fue DRY KISS (No te repitas. Mantenlo simple, estúpido). :-)

Andrejs Cainikovs
fuente
3

Como ya han dicho otros, la importancia es ser coloquial (adaptar estilos y convenciones de nombres a la base de código en la que está escribiendo) y ser coherente.

Durante años, he trabajado en una gran base de código que usa tanto la convención "this->" como el postfix notación de subrayado para las variables miembro. A lo largo de los años también he trabajado en proyectos más pequeños, algunos de los cuales no tenían ningún tipo de convención para nombrar variables miembro, y otros que tenían diferentes convenciones para nombrar variables miembro. De esos proyectos más pequeños, he encontrado consistentemente que los que carecían de cualquier convención son los más difíciles de abordar y comprender rápidamente.

Soy muy anal-retentivo sobre los nombres. Agonizaré sobre el nombre que se atribuirá a una clase o variable hasta el punto de que, si no puedo encontrar algo que siento que es "bueno", elegiré nombrarlo como algo sin sentido y proporcionar un comentario que describa lo que realmente es es. De esa manera, al menos el nombre significa exactamente lo que pretendo que signifique: nada más y nada menos. Y a menudo, después de usarlo por un tiempo, descubro cuál debería ser realmente el nombre y puedo retroceder y modificar o refactorizar adecuadamente.

Un último punto sobre el tema de un IDE haciendo el trabajo: todo es bueno y bueno, pero los IDE a menudo no están disponibles en entornos donde he realizado el trabajo más urgente. A veces, lo único disponible en ese momento es una copia de 'vi'. Además, he visto muchos casos donde la finalización del código IDE ha propagado la estupidez, como la ortografía incorrecta en los nombres. Por lo tanto, prefiero no tener que depender de una muleta IDE.

Chris Cleeland
fuente
3

La idea original para los prefijos en las variables miembro de C ++ era almacenar información de tipo adicional que el compilador no conocía. Entonces, por ejemplo, podría tener una cadena que tiene una longitud fija de caracteres, y otra que es variable y terminada por un '\ 0'. Para el compilador, ambos son char *, pero si intentas copiar de uno a otro, te encontrarás con grandes problemas. Entonces, fuera de mi cabeza,

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

donde "asz" significa que esta variable es "cadena ascii (terminada en cero) y" arr "significa que esta variable es una matriz de caracteres.

Entonces sucede la magia. El compilador estará perfectamente satisfecho con esta declaración:

strcpy(arrWilma, aszFred);

Pero usted, como humano, puede mirarlo y decir "oye, esas variables no son realmente del mismo tipo, no puedo hacer eso".

Desafortunadamente, muchos lugares usan estándares como "m_" para las variables miembro, "i" para enteros, sin importar cómo se usen, "cp" para los punteros de caracteres. En otras palabras, están duplicando lo que el compilador sabe y haciendo que el código sea difícil de leer al mismo tiempo. Creo que esta práctica perniciosa debería estar prohibida por ley y sujeta a severas sanciones.

Finalmente, hay dos puntos que debo mencionar:

  • El uso juicioso de las características de C ++ le permite al compilador conocer la información que tenía que codificar en variables de estilo C sin procesar. Puede hacer clases que solo permitirán operaciones válidas. Esto debe hacerse tanto como sea práctico.
  • Si los bloques de código son tan largas que se le olvida lo que tipo es una variable antes de usarla, que son manera demasiado tiempo. No uses nombres, reorganízate.
AL Flanagan
fuente
Los prefijos que indican el tipo o tipo de variable también son algo que vale la pena discutir, pero me refería principalmente a los prefijos que indican si algo es un miembro / campo (privado). La notación húngara inversa que está mencionando puede ser bastante útil cuando se aplica de manera inteligente (como en su ejemplo). Mi ejemplo favorito donde tiene sentido son las coordenadas relativas y absolutas. cuando vea absX = relX, puede ver claramente que algo puede estar mal. También puede nombrar funciones en consecuencia: absX = absFromRel (relX, offset);
VoidPointer
Nota: la inicialización de aszFred es cuestionable (ofreciendo acceso no constante a una cadena literal), y la inicialización de arrWilma ni siquiera se compilará. (¡Probablemente pretendió declarar arrWilma como una matriz, en lugar de un puntero!) Sin embargo, no hay problema, ya que escribió que está justo en la parte superior de su cabeza ... :-)
Niels Dekker
Vaya, tienes toda la razón. Niños, no intenten eso en casa. Haga esto: 'const char * aszFred = "Hola, soy una cadena terminada en nulo"; char arrWilma [] = {'O', 'o', 'p', 's'}; '
AL Flanagan
3

Nuestro proyecto siempre ha usado "its" como prefijo para los datos de los miembros y "the" como prefijo para los parámetros, sin prefijo para los locales. Es un poco cursi, pero fue adoptado por los primeros desarrolladores de nuestro sistema porque vieron que algunas bibliotecas comerciales de origen lo utilizamos como una convención en ese momento (XVT o RogueWave, tal vez ambas). Entonces obtendrías algo como esto:

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

La gran razón que veo para los prefijos de alcance (y no para otros, odio la notación húngara) es que evita que te metas en problemas escribiendo código donde crees que te estás refiriendo a una variable, pero realmente te estás refiriendo a otra variable con el mismo nombre definido en el ámbito local. También evita el problema de crear nombres de variables para representar ese mismo concepto, pero con diferentes ámbitos, como en el ejemplo anterior. En ese caso, tendría que encontrar algún prefijo o nombre diferente para el parámetro "theName" de todos modos, ¿por qué no hacer una regla coherente que se aplique en todas partes?

Simplemente usar esto-> no es lo suficientemente bueno: no estamos tan interesados ​​en reducir la ambigüedad como en reducir los errores de codificación, y enmascarar nombres con identificadores de ámbito local puede ser una molestia. De acuerdo, algunos compiladores pueden tener la opción de generar advertencias para casos en los que haya enmascarado el nombre en un ámbito más amplio, pero esas advertencias pueden convertirse en una molestia si está trabajando con un gran conjunto de bibliotecas de terceros que han elegido nombres para variables no utilizadas que ocasionalmente chocan con las suyas.

En cuanto a la it / the en sí: honestamente, me resulta más fácil escribir que los guiones bajos (como mecanógrafo táctil, evito los guiones bajos siempre que sea posible, demasiado estiramiento de las filas de inicio), y me parece más legible que un guión misterioso.

Steve Broberg
fuente
Esta es la solución más intuitiva con la curva de aprendizaje más rápida que he escuchado. Desearía que los idiomas hablados fueran más flexibles para manejarlos, de modo que no tuviéramos que pensar en crear nuevas técnicas para resolver ambigüedades en el código.
Guney Ozsan
2

Lo uso porque Intellisense de VC ++ no puede decir cuándo mostrar miembros privados cuando acceden fuera de la clase. La única indicación es un pequeño símbolo de "bloqueo" en el icono de campo en la lista Intellisense. Simplemente facilita la identificación de miembros privados (campos) más fácil. También es un hábito de 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
fuente
2

Creo que, si necesita prefijos para distinguir a los miembros de la clase de los parámetros de la función miembro y las variables locales, la función es demasiado grande o las variables tienen un nombre incorrecto. Si no cabe en la pantalla para que pueda ver fácilmente qué es qué, refactorice.

Dado que a menudo se declaran lejos de donde se usan, creo que las convenciones de nomenclatura para las constantes globales (y las variables globales, aunque en la OMI rara vez es necesario usarlas) tienen sentido. Pero por lo demás, no veo mucha necesidad.

Dicho esto, solía poner un guión bajo al final de todos los miembros de la clase privada. Como todos mis datos son privados, esto implica que los miembros tienen un guión bajo. Por lo general, ya no hago esto en nuevas bases de código, pero dado que, como programador, trabajas principalmente con código antiguo, todavía lo hago mucho. No estoy seguro de si mi tolerancia a este hábito proviene del hecho de que solía hacerlo siempre y todavía lo hago regularmente o si realmente tiene más sentido que el marcado de las variables miembro.

sbi
fuente
2
Esto refleja mucho mi opinión sobre este tema. El código debe ser legible sin recurrir a prefijos. Tal vez no vemos tantos usos de prefijos en lenguajes más modernos porque sus comunidades de usuarios han adoptado la legibilidad un poco más de lo que a veces se ve en C ++. Por supuesto, C ++ puede y debe ser legible. Es solo que se ha escrito una gran cantidad de C ++ ilegible a lo largo de los años.
VoidPointer
2

En python, los guiones bajos dobles se utilizan para emular a miembros privados. Para más detalles ver esta respuesta

Konstantin Tenzin
fuente
1

Es útil diferenciar entre las variables miembro y las variables locales debido a la gestión de la memoria. En términos generales, las variables miembro asignadas al almacenamiento dinámico deben destruirse en el destructor, mientras que las variables locales asignadas al almacenamiento dinámico deben destruirse dentro de ese ámbito. La aplicación de una convención de nomenclatura a las variables miembro facilita la gestión correcta de la memoria.

frankster
fuente
¿Cómo es eso? El destructor no tiene acceso a las variables locales declaradas en otras funciones, por lo que no hay lugar para la confusión allí. Además, las variables locales asignadas al montón no deberían existir . Y las variables miembro asignadas al montón solo deberían existir dentro de las clases RAII, más o menos.
jalf
"las variables locales asignadas al montón no deberían existir" es un poco fuerte. Pero si / cuando los usa, es muy importante asegurarse de que se desasignen correctamente, por lo que una convención de nomenclatura disciplinada para las variables miembro frente a variables locales ayuda enormemente a garantizar esto.
frankster
1

Code Complete recomienda m_varname para las variables miembro.

Si bien nunca pensé que la notación m_ fuera útil, le daría peso a la opinión de McConnell en la construcción de un estándar.

Paul Nathan
fuente
2
No, a menos que explique por qué el guión bajo. Soy un gran admirador de su libro "Rapid Development", que he recomendado aquí varias veces, pero mucho menos de "Code Complete" (que admito que no he leído desde que salió).
1

Casi nunca uso prefijos delante de mis nombres de variables. Si está utilizando un IDE lo suficientemente decente, debería poder refactorizar y encontrar referencias fácilmente. Utilizo nombres muy claros y no temo tener nombres largos y variables. Nunca he tenido problemas con el alcance tampoco con esta filosofía.

La única vez que uso un prefijo sería en la línea de firma. Prefijaré los parámetros a un método con _ para poder programar a la defensiva alrededor de ellos.

James
fuente
1

Nunca deberías necesitar ese prefijo. Si dicho prefijo le ofrece alguna ventaja, su estilo de codificación en general necesita arreglarse, y no es el prefijo lo que impide que su código sea claro. Los nombres típicos de variables malas incluyen "otro" o "2". No arregla eso al exigir que sea más, lo arregla haciendo que el desarrollador piense qué está haciendo esa variable allí en el contexto de esa función. Quizás se refería a remoteSide, o newValue, o secondTestListener o algo en ese ámbito.

Es un anacronismo efectivo que todavía se propaga demasiado. Deja de prefijar tus variables y dales nombres propios cuya claridad refleje cuánto tiempo se usan. Hasta 5 líneas podría llamarlo "i" sin confusión; más allá de 50 líneas necesitas un nombre bastante largo.

dascandy
fuente
1

Me gusta que los nombres de las variables solo den un significado a los valores que contienen, y dejen el nombre de cómo se declaran / implementan. Quiero saber qué significa el valor, punto. Tal vez he realizado más de una cantidad promedio de refactorización, pero encuentro que incorporar cómo se implementa algo en el nombre hace que la refactorización sea más tediosa de lo que debe ser. Los prefijos que indican dónde o cómo se declaran los miembros del objeto son específicos de la implementación.

color = Red;

La mayoría de las veces, no me importa si el Rojo es una enumeración, una estructura o lo que sea, y si la función es tan grande que no puedo recordar si el color se declaró localmente o es un miembro, probablemente sea hora de romper la función en unidades lógicas más pequeñas.

Si su complejidad ciclomática es tan grande que no puede realizar un seguimiento de lo que sucede en el código sin pistas específicas de implementación integradas en los nombres de las cosas, lo más probable es que necesite reducir la complejidad de su función / método.

Principalmente, solo uso 'this' en constructores e inicializadores.

ChrisG65
fuente
0

Uso m_ para las variables miembro solo para aprovechar Intellisense y la funcionalidad IDE relacionada. Cuando estoy codificando la implementación de una clase, puedo escribir m_ y ver el cuadro combinado con todos los miembros de m_ agrupados.

Pero podría vivir sin m_ sin problemas, por supuesto. Es solo mi estilo de trabajo.

Hernán
fuente
También puedes escribirthis->
Toast
0

Según las NORMAS DE CODIFICACIÓN DEL VEHÍCULO AÉREO DE COMBATE DE HUELGA CONJUNTA (diciembre de 2005):

AV Regla 67

Los datos públicos y protegidos solo deben usarse en estructuras, no en clases. Justificación: Una clase puede mantener su invariante controlando el acceso a sus datos. Sin embargo, una clase no puede controlar el acceso a sus miembros si esos miembros no son privados. Por lo tanto, todos los datos en una clase deben ser privados.

Por lo tanto, el prefijo "m" se vuelve inútil ya que todos los datos deben ser privados.

Pero es una buena costumbre usar el prefijo p antes de un puntero, ya que es una variable peligrosa.

Pierre-Louis Deschamps
fuente
0

Muchas de esas convenciones son de una época sin editores sofisticados. Recomendaría usar un IDE adecuado que le permita colorear todo tipo de variable. El color es mucho más fácil de detectar que cualquier prefijo.

Si necesita obtener aún más detalles sobre una variable, cualquier IDE moderno debería poder mostrárselo moviendo el cursor o el cursor sobre él. Y si utiliza una variable de forma incorrecta (por ejemplo, un puntero con el operador.) Obtendrá un error, de todos modos.

Elias Mueller
fuente