resueltos - ¿Por qué usar prefijos en variables miembro en clases de C++?
programacion orientada a objetos ejemplos c++ (28)
Otros intentan hacer cumplir el uso de este miembro> siempre que se usa una variable miembro
Eso es usualmente 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 palabra clave this
.
Entonces, sí, creo que los prefijos aún son útiles. Yo, por mi parte, preferiría escribir ''_'' para acceder a un miembro en lugar de ''this->''.
Una gran cantidad de código C ++ utiliza convenciones sintácticas para marcar las variables de 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 hacer cumplir el uso de este miembro cuando se usa una variable miembro.
En mi experiencia, la mayoría de las bases de códigos más grandes fallan en la aplicación de tales reglas consistentemente.
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 el código de Ruby o Python. Por lo tanto, parece que hay una tendencia en los lenguajes más modernos de no usar marcado especial para las variables miembro.
¿Es esta convención todavía útil hoy en C ++ o es simplemente un anacronismo? Especialmente ya que se usa de forma tan inconsistente en las bibliotecas. ¿No han demostrado los otros idiomas que uno puede prescindir de los prefijos de los miembros?
Últimamente he tendido a preferir el prefijo m_ en lugar de no tener ningún prefijo, las razones no es tanto que sea importante marcar las variables miembro, sino que evita la ambigüedad, digamos que tienes un código como:
void set_foo(int foo) { foo = foo; }
El de causa no funciona, solo se permite un foo
. Entonces tus opciones son:
this->foo = foo;
No me gusta, ya que causa sombreado de parámetros, ya no se pueden usar las advertencias
g++ -Wshadow
, también es más largo para escribir luegom_
. También se encuentra con conflictos de nombres entre variables y funciones cuando tiene unint foo;
y unint foo();
.foo = foo_;
ofoo = arg_foo;
He estado usando eso por un tiempo, pero hace que las listas de argumentos sean desagradables, la documentación no debería tener que ver con la desambigüedad de nombres en la implementación. Los conflictos de nombres entre variables y funciones también existen aquí.
m_foo = foo;
La documentación de la API se mantiene limpia, no obtienes ambigüedad entre las funciones y variables de los miembros, y es más corta para escribir, luego
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_
aplican, pero la rechazo por razones estéticas, un guión bajo final o posterior simplemente hace que la variable parezca incompleta y desequilibrada.m_
solo se ve mejor. Usarm_
también es más extensible, ya que puedes usarg_
para globales ys_
para estática.
PD: La razón por la que no ves m_
en Python o Ruby es porque ambos lenguajes imponen su propio prefijo, Ruby usa @
para las variables de miembro y Python requiere self.
.
Al leer a través de una función miembro, saber quién es el "propietario" de 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 vienen las manzanas y los plátanos, pero ¿qué pasa con las uvas, los melones y las papas fritas? ¿Deberíamos buscar en el espacio de nombres global? En la declaración de clase? ¿La variable es miembro de este objeto o miembro de la clase de este objeto? Sin saber la respuesta a estas preguntas, no puedes 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 mezcla.
El anteponer una etiqueta consistente para variables globales, variables de miembros y miembros estáticos (quizás g_, m_ y s_ respectivamente) aclara instantáneamente la situación.
void Foo::bar( int apples )
{
int bananas = apples + g_grapes;
m_melons = g_grapes * bananas;
s_spuds += m_melons;
}
Puede que les cueste acostumbrarse un poco al principio, pero ¿qué ocurre con la programación? Hubo un día en que incluso {y} te pareció extraño. Y una vez que te acostumbras, 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 prolijo y visualmente disruptivo. 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 una variable "es absolutamente esencial para comprender el significado de la variable". Si eso es así, ¿por qué no agregar un prefijo a cada nombre de variable que identifica su tipo? Con esa lógica, terminas con notación húngara. Pero muchas personas encuentran que la notación húngara es laboriosa, fea e inútil.
void Foo::bar( int iApples )
{
int iBananas = iApples + g_fGrapes;
m_fMelons = g_fGrapes * iBananas;
s_dSpuds += m_fMelons;
}
El húngaro nos dice algo nuevo sobre el código. Ahora comprendemos que hay varias conversiones implícitas 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ña en relación con el costo visual. El sistema de tipo C ++ incluye muchas características para ayudar a que los tipos funcionen bien juntos o para generar una advertencia o error en el compilador. El compilador nos ayuda a manejar los tipos; no necesitamos notación para hacerlo. Podemos inferir con bastante facilidad que las variables en Foo :: bar () son probablemente numéricas, y si eso es todo lo que sabemos, eso es lo suficientemente bueno para obtener una comprensión general de la función. Por lo tanto, el valor de conocer el tipo exacto de cada variable es relativamente bajo. Sin embargo, la fealdad de una variable como "s_dSpuds" (o incluso simplemente "dSpuds") es genial. Entonces, un análisis de costo-beneficio rechaza la notación húngara, mientras que el beneficio de g_, s_, y m_ abruma el costo a los ojos de muchos programadores.
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 no se sienten có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 las variables donde se usan por primera vez disminuye el tiempo que paso mirando hacia atrás para encontrar las declaraciones. (Esto es cierto incluso para pequeñas funciones). Eso me facilita comprender el código que estoy viendo.
Espero que esté suficientemente claro cómo esto se relaciona con las convenciones de nombres de los miembros: cuando los miembros tienen un prefijo uniforme, nunca tengo que mirar hacia atrás en absoluto; 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, al trabajar en entornos en los que se usaban de manera constante, optimicé mi pensamiento para aprovecharlos. Creo que es posible que mucha gente que actualmente se sienta incómoda con ellos también los prefiera, dado el uso constante.
Como otros ya lo han dicho, la importancia es ser coloquial (adaptar los estilos y convenciones de los nombres a la base del código en el 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 uso de una notación de subrayado de postfijo para las variables miembro. Throughout the years I''ve also worked on smaller projects, some of which did not have any sort of convention for naming member variables, and other which had differing conventions for naming member variables. Of those smaller projects, I''ve consistently found those which lacked any convention to be the most difficult to jump into quickly and understand.
I''m very anal-retentive about naming. I will agonize over the name to be ascribed to a class or variable to the point that, if I cannot come up with something that I feel is "good", I will choose to name it something nonsensical and provide a comment describing what it really is. That way, at least the name means exactly what I intend it to mean--nothing more and nothing less. And often, after using it for a little while, I discover what the name should really be and can go back and modify or refactor appropriately.
One last point on the topic of an IDE doing the work--that''s all nice and good, but IDEs are often not available in environments where I have perform the most urgent work. Sometimes the only thing available at that point is a copy of ''vi''. Also, I''ve seen many cases where IDE code completion has propagated stupidity such as incorrect spelling in names. Thus, I prefer to not have to rely on an IDE crutch.
Cuando tiene un método grande o bloques de código, es conveniente saber de inmediato si usa una variable local o un miembro. ¡es para evitar errores y para una mejor claridad!
Esas convenciones son solo eso. La mayoría de las tiendas usan convenciones de códigos para facilitar la lectura del código, de modo que cualquier persona puede ver fácilmente un fragmento de código y descifrar rápidamente cosas como miembros públicos y privados.
Estoy a favor de los prefijos bien hechos .
Creo que la notación húngara (del Sistema) es responsable de la mayor parte del "mal rap" que obtienen los prefijos.
Esta notación es en gran parte 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 comunes punteros a nul-terminados ¡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 el puntero (y pp para el puntero al puntero)
- v para volátil
- s para estática
- i para índices e iteradores
- e para eventos
Cuando deseo hacer que el tipo sea claro, uso los sufijos estándar (por ejemplo, List, 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 tiene que ser mucho más cuidadoso con los punteros - NULL, aritmética del puntero, etc.), pero todos los demás son muy útiles.
Por ejemplo, puede usar el mismo nombre de variable de varias maneras en una sola función: (aquí un ejemplo de C ++, pero se aplica por igual a muchos idiomas)
MyClass::MyClass(int numItems)
{
mNumItems = numItems;
for (int iItem = 0; iItem < mNumItems; iItem++)
{
Item *pItem = new Item();
itemList[iItem] = pItem;
}
}
Puedes ver aquí:
- Sin confusión entre miembro y parámetro
- Sin 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 trampas de nombres genéricos (vagos) como "recuento", "índice".
- Los prefijos reducen el tipeo (más corto y funciona mejor con autocompletar) que alternativas como "itemIndex" y "itemPtr"
Otro gran punto de los iteradores "iName" es que nunca indexo una matriz con el índice incorrecto, y si copio un bucle dentro de otro bucle, no es necesario refactorizar una de las variables de índice de bucle.
Compare este ejemplo irrealmente 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 que 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 IDEs 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 comprendido. Puedo copiar dos líneas de código en un correo electrónico o documento, y cualquiera que lea ese fragmento puede diferenciar entre todos los miembros, constantes, punteros, índices, etc. No tengo que agregar "oh, y tenga cuidado porque ''datos'' es un puntero a un puntero ", porque se llama ''ppData''.
Y por la misma razón, no tengo que mover los ojos fuera de una línea de código para poder entenderlo. No tengo que buscar el código para encontrar si ''data'' es un local, parámetro, miembro o constante. No tengo que mover la mano hacia el mouse para pasar el puntero sobre ''datos'' y 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 hacia arriba o hacia abajo o esperando.
(Si no crees que pierdes el tiempo buscando las cosas que salen, encuentra algún código que hayas escrito hace un año y que no hayas visto desde entonces. Abre el archivo y salta a la mitad sin leerlo. Mira cómo Hasta ahora puede leer desde este punto antes de no saber si algo es miembro, parámetro o local. Ahora salte a otra ubicación aleatoria ... Esto es lo que hacemos todos los días cuando estamos solos caminando a través del 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 vergonzosa, 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 exige una ortografía consistente del nombre).
La notación "this" tiene la intención de resolver la ambigüedad , pero ¿por qué alguien escribiría código deliberadamente ambiguo? La ambigüedad dará lugar 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, no ambigua y consistente.
El último gran beneficio es con Intellisense y autocompletado. Intente utilizar Intellisense en Windows Forms para encontrar un evento: tiene que desplazarse a través de cientos de misteriosos métodos de clase base que nunca necesitará llamar para encontrar los eventos. Pero si cada evento tuviera un prefijo "e", se incluirían automáticamente en un grupo debajo de "e". Por lo tanto, el prefijo funciona para agrupar los miembros, constelaciones, eventos, etc. en la lista intellisense, lo que hace que sea mucho más rápido y fácil encontrar los nombres que desee. (Por lo general, un método puede tener entre 20 y 50 valores (locals, params, members, consts, events) que son accesibles en su alcance. Pero después de tipear el prefijo (quiero usar un índice ahora, entonces escribo ''i. .. ''), se me presentan solo 2-5 opciones de autocompletar. La gente'' de tipo extra ''atribuye a los prefijos y nombres significativos reduce drásticamente el espacio de búsqueda y acelera de forma considerable la velocidad de desarrollo)
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 prefijo son malos / malvados" . Estoy de acuerdo en que "m_lpsz" y su tipo están mal pensados y son totalmente inútiles. Es por eso que aconsejo utilizar una notación bien diseñada diseñada para respaldar sus requisitos, en lugar de copiar algo que no es apropiado para su contexto. (Use la herramienta correcta para el trabajo).
"Si cambio el uso de algo, debo cambiarle el nombre" . Sí, por supuesto que sí, de eso se trata la refactorización, y de por qué los IDE tienen herramientas de refactorización para hacer este trabajo de forma rápida y sin complicaciones. Incluso sin prefijos, cambiar el uso de una variable casi seguro significa que su nombre debe ser cambiado.
"Los prefijos me confunden" . Al igual que todas las herramientas hasta que aprenda a usarlo. Una vez que tu cerebro se haya acostumbrado a los patrones de nombres, filtrará la información automáticamente y no te importará que los prefijos ya estén allí. Pero debe usar un esquema como este sólidamente durante una semana o dos antes de que realmente se vuelva "fluido". Y es entonces cuando mucha gente mira el código antiguo y comienza 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 otro lugar del código o recordando cada pequeño detalle cuando la respuesta está en el lugar en el que su ojo ya está enfocado.
(Parte de) esa información se puede encontrar simplemente esperando a que aparezca una información sobre herramientas en mi variable . Sí. Cuando se admite, para algunos tipos de prefijo, cuando el 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 al instante. Siento que el prefijo es un enfoque más simple, más confiable y más eficiente.
"Es más tipeo" . De Verdad? Un personaje completo más? ¿O lo es? Con las herramientas de autocompletado IDE, a menudo reducirá el tipeo, ya que cada carácter de prefijo reduce significativamente el espacio de búsqueda. Presiona "e" y los tres eventos en tu clase aparecerán en intellisense. Presione "c" y las cinco constantes se enumeran.
"Puedo usar
this->
lugar dem
" . Bueno, sí, puedes. ¡Pero eso es solo un prefijo mucho más feo y más detallado! Solo que conlleva un riesgo mucho mayor (especialmente en equipos) porque para el compilador es opcional , y por lo tanto su uso es frecuentemente inconsistente.m
por otro lado es breve, claro, explícito y no opcional, por lo que es mucho más difícil cometer errores al usarlo.
La razón principal para un prefijo 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 se podría llamar nombre completo en este caso. Necesita cambiar el nombre de la función miembro a get_full_name () o decorar la variable miembro de alguna manera.
No creo que una sintaxis tenga valor real sobre otra. Todo se reduce, como mencionaste, a la uniformidad en todos los archivos fuente.
El único punto donde encuentro tales reglas interesantes es cuando necesito 2 cosas nombradas idénticamente, por ejemplo:
void myFunc(int index){
this->index = index;
}
void myFunc(int index){
m_index = index;
}
Lo uso para diferenciar los dos. Además, cuando envuelvo las llamadas, como desde Windows Dll, RecvPacket (...) desde el Dll podría estar envuelto en RecvPacket (...) en mi código. En estas ocasiones en particular, el uso de un prefijo como "_" puede hacer que los dos se vean iguales, fácil de identificar cuál es cuál, pero diferente para el compilador
No puedo decir qué tan amplio es, pero hablando personalmente, siempre (y siempre) he puesto como prefijo mis variables de miembro con ''m''. P.ej:
class Person {
....
private:
std::string mName;
};
Es la única forma de prefijo que uso (soy notación muy anti-húngara) pero me ha sido muy útil a lo largo de los años. Como comentario adicional, generalmente detesto el uso de caracteres de subrayado en los nombres (o en cualquier otro lugar), pero hago una excepción para los nombres de macro del preprocesador, ya que generalmente están en mayúsculas.
OMI, esto es personal. No estoy poniendo ningún prefijo en absoluto. De todos modos, si se dice que el código es público, creo que debería tener algunos prefijos, por lo que puede ser más legible.
A menudo, las grandes empresas están usando sus propias ''reglas de desarrollador''.
Por cierto, el más divertido y más inteligente que vi fue DRY KISS (no repetir, manténlo simple, estúpido). :-)
Otros lenguajes utilizarán convenciones de codificación, simplemente tienden a ser diferentes. C #, por ejemplo, probablemente tenga dos estilos diferentes que las personas tienden a usar, ya sea uno de los métodos 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 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 necesita un prefijo m para las variables miembro porque prefija su uso con esto. indica lo mismo en un método aplicado por el compilador.
Esto no necesariamente significa que los otros métodos son malos, las personas se apegan a lo que funciona.
Por lo general, no uso un prefijo para las variables miembro.
Solía usar un prefijo m
, hasta que alguien señaló 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 prefijo this->
, 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 obvio contra esto es "sí, pero luego no se puede ver a simple vista si una variable es un miembro de la clase o no".
A lo que les 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 una ventaja 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 principales para los miembros de tu clase. Te acercas incómodamente a los nombres que están reservados por la implementación.
El estándar reserva todos los nombres comenzando con doble guión bajo o guión bajo seguido de letra mayúscula. También reserva todos los nombres comenzando con un único 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úsculas o que de otra forma rompa una de las reglas anteriores.
Por lo tanto, es más fácil simplemente evitar los guiones bajos principales. Use un guión bajo postfijo, o un prefijo m_
o solo m
si desea codificar el alcance en el nombre de la variable.
Prefiero los guiones bajos de postfix, como los siguientes:
class Foo
{
private:
int bar_;
public:
int bar() { return bar_; }
};
Tienes que tener cuidado con el uso de un guion bajo. Un subrayado inicial 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 no se permiten subrayados iniciales antes de letras minúsculas. En mi caso específico, descubrí que el _L pasó a estar reservado por Visual C ++ 2005 y el choque creó algunos resultados inesperados.
Estoy al margen 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 ++?
According to JOINT STRIKE FIGHTER AIR VEHICLE C++ CODING STANDARDS (december 2005):
AV Rule 67
Public and protected data should only be used in structs—not classes. Rationale: A class is able to maintain its invariant by controlling access to its data. However, a class cannot control access to its members if those members non-private. Hence all data in a class should be private.
Thus, the "m" prefix becomes unuseful as all data should be private.
Pero es una buena costumbre usar el prefijo p antes de un puntero ya que es una variable peligrosa.
Code Complete recommends m_varname for member variables.
While I''ve never thought the m_ notation useful, I would give McConnell''s opinion weight in building a standard.
I almost never use prefixes in front of my variable names. If you''re using a decent enough IDE you should be able to refactor and find references easily. I use very clear names and am not afraid of having long variable names. I''ve never had trouble with scope either with this philosophy.
The only time I use a prefix would be on the signature line. I''ll prefix parameters to a method with _ so I can program defensively around them.
I like variable names to give only a meaning to the values they contain, and leave how they are declared/implemented out of the name. I want to know what the value means, period. Maybe I''ve done more than an average amount of refactoring, but I find that embedding how something is implemented in the name makes refactoring more tedious than it needs to be. Prefixes indicating where or how object members are declared are implementation specific.
color = Red;
Most of the time, I don''t care if Red is an enum, a struct, or whatever, and if the function is so large that I can''t remember if color was declared locally or is a member, it''s probably time to break the function into smaller logical units.
If your cyclomatic complexity is so great that you can''t keep track of what is going on in the code without implementation-specific clues embedded in the names of things, most likely you need to reduce the complexity of your function/method.
Mostly, I only use ''this'' in constructors and initializers.
I think that, if you need prefixes to distinguish class members from member function parameters and local variables, either the function is too big or the variables are badly named. If it doesn''t fit on the screen so you can easily see what is what, refactor.
Given that they often are declared far from where they are used, I find that naming conventions for global constants (and global variables, although IMO there''s rarely ever a need to use those) make sense. But otherwise, I don''t see much need.
That said, I used to put an underscore at the end of all private class members. Since all my data is private, this implies members have a trailing underscore. I usually don''t do this anymore in new code bases, but since, as a programmer, you mostly work with old code, I still do this a lot. I''m not sure whether my tolerance for this habit comes from the fact that I used to do this always and am still doing it regularly or whether it really makes more sense than the marking of member variables.
I use it because VC++''s Intellisense can''t tell when to show private members when accessing out of the class. The only indication is a little "lock" symbol on the field icon in the Intellisense list. It just makes it easier to identify private members(fields) easier. Also a habit from C# to be honest.
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;
}
I use m_ for member variables just to take advantage of Intellisense and related IDE-functionality. When I''m coding the implementation of a class I can type m_ and see the combobox with all m_ members grouped together.
But I could live without m_ ''s without problem, of course. It''s just my style of work.
In python leading double underscores are used to emulate private members. For more details see this answer
It is useful to differentiate between member variables and local variables due to memory management. Broadly speaking, heap-allocated member variables should be destroyed in the destructor, while heap-allocated local variables should be destroyed within that scope. Applying a naming convention to member variables facilitates correct memory management.
Our project has always used "its" as a prefix for member data, and "the" as a prefix for parameters, with no prefix for locals. It''s a little cutesy, but it was adopted by the early developers of our system because they saw it used as a convention by some commercial source libraries we were using at the time (either XVT or RogueWave - maybe both). So you''d get something like this:
void
MyClass::SetName(const RWCString &theName)
{
itsName = theName;
}
The big reason I see for scoping prefixes (and no others - I hate Hungarian notation) is that it prevents you from getting into trouble by writing code where you think you''re referring to one variable, but you''re really referring to another variable with the same name defined in the local scope. It also avoids the problem of coming up with a variable names to represent that same concept, but with different scopes, like the example above. In that case, you would have to come up with some prefix or different name for the parameter "theName" anyway - why not make a consistent rule that applies everywhere.
Just using this-> isn''t really good enough - we''re not as interested in reducing ambiguity as we are in reducing coding errors, and masking names with locally scoped identifiers can be a pain. Granted, some compilers may have the option to raise warnings for cases where you''ve masked the name in a larger scope, but those warnings may become a nuisance if you''re working with a large set of third party libraries that happen to have chosen names for unused variables that occasionally collide with your own.
As for the its/the itself - I honestly find it easier to type than underscores (as a touch typist, I avoid underscores whenever possible - too much stretching off the home rows), and I find it more readable than a mysterious underscore.
The original idea for prefixes on C++ member variables was to store additional type information that the compiler didn''t know about. So for example, you could have a string that''s a fixed length of chars, and another that''s variable and terminated by a ''/0''. To the compiler they''re both char *
, but if you try to copy from one to the other you get in huge trouble. So, off the top of my head,
char *aszFred = "Hi I''m a null-terminated string";
char *arrWilma = {''O'', ''o'', ''p'', ''s''};
where "asz" means this variable is "ascii string (zero-terminated) and "arr" means this variable is a character array.
Then the magic happens. The compiler will be perfectly happy with this statement:
strcpy(arrWilma, aszFred);
But you, as a human, can look at it and say "hey, those variables aren''t really the same type, I can''t do that".
Unfortunately a lot places use standards such as "m_" for member variables, "i" for integers no matter how used, "cp" for char pointers. In other words they''re duplicating what the compiler knows, and making the code hard to read at the same time. I believe this pernicious practice should be outlawed by statute and subject to harsh penalties.
Finally, there''s two points I should mention:
- Judicious use of C++ features allows the compiler to know the information you had to encode in raw C-style variables. You can make classes that will only allow valid operations. This should be done as much as practical.
- If your code blocks are so long that you forget what type a variable is before you use it, they are way too long. Don''t use names, re-organize.
You should never need such a prefix. If such a prefix offers you any advantage, your coding style in general needs fixing, and it''s not the prefix that''s keeping your code from being clear. Typical bad variable names include "other" or "2". You do not fix that with requiring it to be mOther, you fix it by getting the developer to think about what that variable is doing there in the context of that function. Perhaps he meant remoteSide, or newValue, or secondTestListener or something in that scope.
It''s an effective anachronism that''s still propagated too far. Stop prefixing your variables and give them proper names whose clarity reflects how long they''re used. Up to 5 lines you could call it "i" without confusion; beyond 50 lines you need a pretty long name.