Serie de habilidades de C++: estándares de codificación (Guía de estilo de programación de Google C++)

Insertar descripción de la imagen aquí

现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。
Now everything is for the future of dream weaving wings, let the dream fly in reality.

Estándares de codificación (Guía de estilo de Google C++)

Ejemplo de estándar de codificación (Guía de estilo de Google C++)

Insertar descripción de la imagen aquí

0.Fondo

La mayoría de los proyectos de código abierto de Google se desarrollan utilizando C++. Todo programador de C++ también sabe que C++ tiene muchas características de lenguaje potentes, pero esta potencia conduce inevitablemente a su complejidad, que hará que el código sea más propenso a errores y difícil de leer y mantener.
El propósito de esta guía es evitar su complejidad detallando qué hacer y qué no hacer al codificar en C++. Estas reglas facilitan la administración y al mismo tiempo permiten que el código utilice las funciones del lenguaje C++ de manera eficiente.
El estilo, también conocido como legibilidad, se refiere principalmente a los hábitos que gobiernan el código C++. Usar el término estilo es un nombre poco apropiado, ya que estos hábitos van mucho más allá de los formatos de archivo de código fuente.
Una forma de hacer que su código sea manejable es mejorar la coherencia del código. Es importante hacer que su código sea comprensible para los demás. Mantener un estilo de programación unificado significa que el significado de varios símbolos se puede inferir fácilmente basándose en reglas de "coincidencia de patrones". Crear modismos y patrones comunes y necesarios puede hacer que el código sea más fácil de entender. En algunos casos, cambiar algunos estilos de programación puede ser una buena opción, pero aun así debemos seguir el principio de coherencia y tratar de no hacerlo.
Otro punto destacado en esta guía es la abundancia de funciones de C++. C++ es un lenguaje gigante que contiene muchas funciones avanzadas. En algunos casos, restringiremos o incluso prohibiremos el uso de ciertas funciones para simplificar el código y evitar diversos problemas que puedan causarlo. La guía enumera dichas funciones y explica por qué. Las características son de uso limitado.
Los proyectos de código abierto desarrollados por Google cumplirán con estas directrices.
Nota: Esta guía no es un tutorial de C++; asumimos que el lector ya está familiarizado con C++.

1.Archivo de encabezado

Por lo general, cada archivo .cc (archivo fuente C++) tiene un archivo .h correspondiente (archivo de encabezado C++), existen algunas excepciones, como el código de prueba unitaria y los archivos .cc que solo contienen main().
El uso adecuado de los archivos de encabezado puede mejorar en gran medida la legibilidad del código, el tamaño del archivo y el rendimiento.
Las siguientes reglas lo guiarán para evitar varios problemas al utilizar archivos de encabezado.

1.1 Protección de #definir

Todos los archivos de encabezado deben usar #define para evitar inclusiones múltiples de archivos de encabezado. El formato de nomenclatura debe ser: HPara
garantizar la unicidad, el nombre del archivo de encabezado debe basarse en la ruta completa del árbol de código fuente del proyecto en el que se encuentra. Por ejemplo, el archivo de encabezado foo/src/bar/baz.h en el proyecto foo está protegido de la siguiente manera:
#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_

#endif // FOO_BAR_BAZ_H_

1.2 Dependencias del archivo de encabezado

Utilice declaraciones directas para minimizar la cantidad de #includes en archivos .h.
Cuando se incluye un archivo de encabezado, también se introduce una nueva dependencia. Siempre que se modifique el archivo de encabezado, el código debe volver a compilarse. Si su archivo de encabezado incluye otros archivos de encabezado, cualquier cambio en esos archivos de encabezado también hará que se vuelva a compilar el código que incluye su archivo de encabezado. Por lo tanto, preferimos incluir la menor cantidad de archivos de encabezado posible, especialmente aquellos incluidos dentro de otros archivos de encabezado.
El uso de declaraciones directas puede reducir significativamente la cantidad de archivos de encabezado que deben incluirse. Por ejemplo: la clase Archivo se usa en el archivo de encabezado, pero no es necesario acceder a la declaración del Archivo, entonces la clase Archivo solo necesita declararse previamente en el archivo de encabezado; no es necesario #incluir "archivo/ base/archivo.h".
¿Cómo puedo usar la clase Foo en un archivo de encabezado sin acceder a la definición de clase?

  1. Declare el tipo de miembro de datos como Foo * o Foo &;
  2. Las funciones con parámetros y tipos de valor de retorno de Foo solo se declaran (pero la implementación no está definida);
  3. El tipo de miembros de datos estáticos se puede declarar como Foo porque los miembros de datos estáticos se definen fuera de la definición de clase.
    Por otro lado, si su clase es una subclase de Foo o contiene miembros de datos no estáticos de tipo Foo, debe incluir un archivo de encabezado para ella.
    A veces tiene sentido utilizar miembros punteros (mejor aún, con alcance_ptr) en lugar de miembros objeto. Sin embargo, este enfoque reducirá la legibilidad del código y la eficiencia de ejecución. Si es solo para incluir menos archivos de encabezado, es mejor no reemplazarlos así.
    Por supuesto, el archivo .cc necesita la parte de definición de la clase utilizada de todos modos y, naturalmente, contendrá varios archivos de encabezado.
    Nota del traductor: no confíe en las definiciones si puede confiar en las declaraciones.

1.3.Funciones en línea

Solo cuando una función tiene 10 líneas o menos se define como una función en línea.
Definición: cuando una función se declara como una función en línea, el compilador puede expandirla en línea sin llamar a la función en línea de acuerdo con el mecanismo habitual de llamada a funciones.
Ventajas: cuando el cuerpo de la función es relativamente pequeño, incorporar la función puede hacer que el código de destino sea más eficiente. Para funciones de acceso (accesor, mutador) y otras funciones de ejecución de teclas cortas.
Desventajas: El abuso de la inserción hará que el programa se ralentice. La inserción puede aumentar o disminuir la cantidad de código de destino, dependiendo del tamaño de la función que se está insertando. La incorporación de funciones de acceso más pequeñas generalmente reduce el tamaño del código, pero la incorporación de una función muy grande (Nota del traductor: si el compilador lo permite) aumentará drásticamente el tamaño del código. En los procesadores modernos, el código más pequeño tiende a ejecutarse más rápido debido a una mejor utilización del caché de instrucciones.
Conclusión: una buena regla general es no incorporar funciones que excedan las 10 líneas. Los destructores deben tratarse con precaución, los destructores suelen ser más largos de lo que parecen debido a algunos miembros implícitos y se llama al destructor de clase base (si lo hay).
Otra regla general útil: no es bueno incorporar funciones que contengan bucles o declaraciones de cambio, a menos que en la mayoría de los casos estos bucles o declaraciones de cambio nunca se ejecuten.
Es importante destacar que las funciones virtuales y recursivas no son necesariamente funciones en línea, incluso si se declaran en línea. Generalmente, las funciones recursivas no deben declararse en línea (función recursiva). La razón principal para incluir el destructor es que está definido en la definición de clase, por conveniencia o para documentar su comportamiento.

1.4.-archivo inl.h

La definición de funciones en línea complejas debe colocarse en un archivo de encabezado con el sufijo -inl.h.
Dar la definición de una función en línea en un archivo de encabezado permite al compilador expandirla en línea en el sitio de llamada. Sin embargo, el código de implementación debe colocarse completamente en el archivo .cc. No queremos que aparezca demasiado código de implementación en el archivo .h a menos que existan ventajas obvias en legibilidad y eficiencia.
Si la definición de la función en línea es relativamente corta y la lógica es relativamente simple, su código de implementación se puede colocar en el archivo .h. Por ejemplo, la implementación de funciones de acceso se coloca naturalmente en la definición de clase. Para facilitar la implementación y la llamada, también se pueden colocar funciones en línea más complejas en archivos .h. Si cree que esto hará que el archivo de encabezado parezca voluminoso, también puede separarlo en un -inl.h separado. Esto separa la implementación de la definición de clase e incluye -inl.h donde se ubica la implementación cuando es necesario.
El archivo -inl.h también se puede utilizar para definir plantillas de funciones, lo que hace que la definición de la plantilla sea más legible.
Una cosa para recordar es que -inl.h, al igual que otros archivos de encabezado, también necesita protección #define.

1.5 Orden de parámetros de función

Al definir una función, el orden de los parámetros es: los parámetros de entrada primero, los parámetros de salida al final.
Los parámetros de función C / C ++ se dividen en dos tipos: parámetros de entrada y parámetros de salida, a veces los parámetros de entrada también se emiten (Nota del traductor: cuando se modifica el valor). Los parámetros de entrada generalmente se pasan por valor o referencia constante (referencias constantes), y los parámetros de salida o parámetros de entrada/salida son punteros no constantes (punteros no constantes). Al ordenar los parámetros, coloque todos los parámetros de entrada antes de los parámetros de salida. No lo ponga al final solo porque es un parámetro recién agregado, pero aún así debe colocarse antes de los parámetros de salida.
Esta no es una regla que deba seguirse, y los parámetros mixtos de entrada/salida (normalmente variables de clase/estructura) harán que la regla sea difícil de seguir.

1.6 Incluir el nombre y orden de los archivos

Estandarizar el orden de inclusión puede mejorar la legibilidad y evitar dependencias ocultas (Nota del traductor: las dependencias ocultas se refieren principalmente a archivos incluidos durante la compilación). El orden es el siguiente: biblioteca C, biblioteca C ++, .h de otras bibliotecas, proyectos .h dentro.
Los archivos de encabezado dentro del proyecto deben organizarse de acuerdo con la estructura de árbol del directorio del código fuente del proyecto y evitar el uso de rutas de archivos UNIX (directorio actual) y... (directorio principal). Por ejemplo, google-awesome-project/src/base/logging.h debe incluirse así:
#include "base/logging.h"
La función principal de dir/foo.cc es ejecutar o probar la funcionalidad de dir2/ foo2.h, el orden de los archivos de encabezado incluidos en foo.cc es el siguiente:
dir2/foo2.h (posición de prioridad, los detalles son los siguientes)
Archivos del sistema C Archivos
del sistema C++
Otros archivos de encabezado de la biblioteca Este método de clasificación de
los archivos de encabezado en este El proyecto puede reducir eficazmente las dependencias ocultas. Esperamos
que cada archivo de encabezado se compile de forma independiente. La forma más sencilla de lograrlo es incluirlo como primer archivo .h en el .cc correspondiente.
dir/foo.cc y dir2/foo2.h generalmente se encuentran en el mismo directorio (como base/basictypes_unittest.cc y base/basictypes.h), pero también pueden estar en directorios diferentes.
El orden alfabético de los archivos de encabezado en el mismo directorio es una buena opción.
Por ejemplo, el orden de inclusión de google-awesome-project/src/foo/internal/fooserver.cc es el siguiente:
#include "foo/public/fooserver.h" // Prioridad
#include <sys/types.h>
#incluye <unistd.h>
#incluye <hash_map>
#incluye
#incluye “base/basictypes.h”
#incluye “base/commandlineflags.h”
#incluye “foo/public/bar.h”

1.7.Resumen

  1. Evitar inclusiones múltiples es el requisito más básico al aprender a programar;
  2. La declaración directa tiene como objetivo reducir las dependencias de compilación y evitar que la modificación de un archivo de encabezado cause un efecto dominó;
  3. El uso razonable de funciones en línea puede mejorar la eficiencia de ejecución del código;
  4. -inl.h puede mejorar la legibilidad del código (generalmente no se usa :D);
  5. Estandarizar el orden de los parámetros de función puede mejorar la legibilidad y la facilidad de mantenimiento (tiene un ligero impacto en el espacio de pila de los parámetros de función, solía juntarlos en su mayoría del mismo tipo);
  6. Los nombres de los archivos incluidos usan . y... aunque es conveniente pero propenso a la confusión. Usar una ruta de proyecto relativamente completa parece muy claro y organizado. Además de ser hermoso, el orden de los archivos incluidos, lo más importante es que puede reduzca las dependencias ocultas y haga que cada archivo de encabezado Al compilar en el lugar que "más necesita compilación" (correspondiente al archivo fuente: D), algunas personas sugieren que el archivo de la biblioteca se coloque al final. De esta manera, los archivos en el proyecto fallará primero y los archivos de encabezado se colocarán al frente del archivo fuente correspondiente, lo que es suficiente para garantizar que los errores internos se descubran rápidamente.

2. Alcance

2.1 Espacios de nombres

En archivos .cc, se recomienda utilizar espacios de nombres sin nombre (Nota del traductor: los espacios de nombres sin nombre son como clases sin nombre y parece que rara vez se introducen:-(). Cuando se utilizan espacios de nombres con nombre, su nombre puede basarse en el proyecto o la ruta nombre, no utilice el indicador de uso.
Definición: el espacio de nombres subdivide el ámbito global en diferentes ámbitos con nombre, lo que puede prevenir eficazmente conflictos de nombres en el ámbito global. Ventajas: el
espacio de nombres proporciona Por supuesto, la clase también proporciona un eje de nombres (anidado) (Nota del traductor: divida el nombre en diferentes espacios de nombres).
Por ejemplo, dos proyectos diferentes tienen una clase Foo en el alcance global, lo que provoca conflictos durante la compilación o el tiempo de ejecución. Si cada proyecto coloca el código en un espacio de nombres diferente, proyecto1 ::Foo Naturalmente, no habrá conflicto con project2::Foo como símbolos diferentes.
Desventajas: los espacios de nombres son confusos porque proporcionan ejes de nombres adicionales (anidados) como clases. El uso de espacios sin nombre en archivos de encabezado es fácil de violar la regla de definición única de C++ ( ODR)
Conclusión: Utilice los espacios de nombres de forma adecuada según las estrategias que se mencionan a continuación.

2.1.1.Espacios de nombres sin nombre

En archivos .cc, se permite o incluso se recomienda el uso de espacios de nombres sin nombre para evitar conflictos de nombres en tiempo de ejecución:
namespace { // En archivos .cc
// no es necesario sangrar el contenido del espacio de nombres
enum { UNUSED, EOF, ERROR }; // Símbolos de uso frecuente
bool AtEof() { return pos_ == EOF; } // Utilice el símbolo EOF dentro de este espacio de nombres
} // espacio de nombres
Sin embargo, las declaraciones de alcance de archivos asociadas con una clase específica se declaran como tipos en esa clase, un miembro de datos estáticos o una función de miembro estático, en lugar de un miembro de un espacio de nombres sin nombre. Como se muestra arriba, los espacios de nombres sin nombre terminan con el comentario // del espacio de nombres.
Los espacios de nombres sin nombre no se pueden utilizar en archivos .h.

2.1.2 Espacios de nombres con nombre

El espacio de nombres con nombre se utiliza de la siguiente manera:
el espacio de nombres encapsula todo el archivo fuente, excepto la inclusión del archivo, la declaración/definición de identificadores globales y la declaración directa de la clase para distinguirlo de otros espacios de nombres.
// espacio de nombres del archivo .h
mynamespace { // Todas las declaraciones se colocan en el espacio de nombres // Tenga cuidado de no utilizar la clase de sangría MyClass { public: void Foo(); }; } // espacio de nombres mynamespace // espacio de nombres del archivo .cc mynamespace { // Las definiciones de funciones se colocan en el espacio de nombres void MyClass::Foo() { } } // espacio de nombres mynamespace Generalmente los archivos .cc contendrán detalles cada vez más complejos, incluidas referencias a clases en otros espacios de nombres. #include “ah” DEFINE_bool(someflag, false, “dummy flag”); class C; // Predeclaración de la clase C en el espacio de nombres global namespace a { class A; } // Clase a en el espacio de nombres a:: Espacio de nombres de declaración directa de A b { …código para b… // código en b






















} // espacio de nombres b
No declare nada bajo el espacio de nombres std, incluidas las declaraciones directas de clases de biblioteca estándar. Declarar entidades bajo estándar puede generar un comportamiento poco claro, por ejemplo, no portátil. Para declarar una entidad en la biblioteca estándar, debe incluir el archivo de encabezado correspondiente.
Es mejor no utilizar la directiva de uso para garantizar que todos los nombres del espacio de nombres se puedan utilizar normalmente.
// Prohibido: contaminar el espacio de nombres
usando el espacio de nombres foo;
el uso se puede usar en funciones, métodos o clases en archivos .cc, archivos .h.
// Permitido: en archivos .cc // En archivos .h, el uso de ::foo::bar
debe usarse dentro de funciones, métodos o clases ; en archivos .cc, archivos .h, funciones, métodos o clases, también alias de espacios de nombres puede ser usado. // Permitido: en archivos .cc // En archivos .h, debe usarse dentro de funciones, métodos o clases



espacio de nombres fbz = ::foo::bar::baz;

2.2 Clase anidada

Al exponer clases anidadas como parte de una interfaz, es mejor colocar la declaración de la clase anidada en un espacio de nombres, aunque es posible mantenerlas directamente en el ámbito global.
Definición: Puede definir otra clase dentro de una clase. Las clases anidadas también se denominan clases miembro.
class Foo { private: // Bar es una clase miembro anidada en Foo class Bar { ... }; }; Ventajas: útil cuando las clases (miembros) anidadas solo se usan en la clase adjunta, colocándolas en el ámbito de la clase anidada como un miembro de la clase anidada no contaminará otros ámbitos con la misma clase. Puede declarar previamente la clase anidada en la clase anidada y definir la clase anidada en el archivo .cc para evitar incluir la definición de la clase anidada en la clase anidada, porque la definición de la clase anidada generalmente solo es relevante para la implementación. . Desventaja: las clases anidadas solo se pueden declarar hacia adelante en la definición de la clase anidada. Por lo tanto, cualquier archivo de encabezado que utilice punteros Foo::Bar* debe contener la declaración completa de Foo. Conclusión: no defina clases anidadas como públicas a menos que formen parte de una interfaz; por ejemplo, un método utiliza un conjunto de opciones de la clase.








2.3. Funciones no miembros, miembros estáticos y globales

Utilice funciones que no sean miembros o funciones miembro estáticas en el espacio de nombres e intente no utilizar funciones globales.
Ventajas: en algunos casos, las funciones no miembros y las funciones miembro estáticas son muy útiles. Colocar funciones no miembros en el espacio de nombres puede evitar la contaminación del alcance global.
Desventajas: puede tener más sentido hacer que las funciones no miembros y las funciones miembro estáticas sean miembros de una nueva clase, especialmente cuando necesitan acceder a recursos externos o tienen dependencias importantes.
Conclusión:
a veces es beneficioso, o incluso necesario, no limitar una función a la entidad de la clase, ya sea como miembro estático o como función no miembro. Las funciones que no son miembros no deben depender de variables externas y deben ubicarse en un espacio de nombres tanto como sea posible. En lugar de crear una clase simplemente para encapsular una serie de funciones miembro estáticas que no comparten ningún dato estático, es mejor utilizar un espacio de nombres.
Las funciones definidas en la misma unidad de compilación que son llamadas directamente por otras unidades de compilación pueden introducir dependencias de conexión y acoplamiento innecesarias; las funciones miembro estáticas son particularmente sensibles a esto. Considere realizar la extracción en una nueva clase o colocar la función en un espacio de nombres de biblioteca independiente.
Si realmente necesita definir una función que no sea miembro y usarla solo en un archivo .cc, puede usar un espacio de nombres sin nombre o una asociación estática (como static int Foo() {…}) para limitar su alcance.

2.4.Variables locales

Coloque las variables de función en el alcance más pequeño posible e inicialícelas al declararlas.
C++ permite declarar variables en cualquier lugar de una función. Recomendamos declarar variables en el alcance más pequeño posible, lo más cerca posible del primer uso. Esto hace que el código sea más fácil de leer, lo que facilita la localización de la ubicación de declaración de la variable, el tipo de variable y el valor inicial. En particular, se debe utilizar inicialización en lugar de declaración+asignación.
int i;
i = f(); // Malo - separación de inicialización y declaración
int j = g(); // Bueno - declaración durante la inicialización Nota
: gcc se puede ejecutar correctamente para (int i = 0; i < 10; + +i) (el alcance de i está limitado a bucles for), por lo que puedo reutilizarlo en otros bucles for. En las declaraciones if y while, las declaraciones de alcance también son correctas.
while (const char* p = strchr(str, '/')) str = p + 1;
Nota: Si la variable es un objeto, se debe llamar a su constructor cada vez que ingresa al alcance, y se debe llamar a su constructor cada momento en que sale del alcance su destructor.
// Implementación ineficiente
for (int i = 0; i < 1000000; ++i) { Foo f; // ¡El constructor y el destructor se llaman 1000000 veces cada uno! f.DoSomething(i); } Es mucho más eficiente declarar variables similares fuera del alcance del bucle: Foo f; // El constructor y el destructor solo se llaman una vez for (int i = 0; i < 1000000; ++ i) { f.Hacer algo(i); }







2.5.Variables Globales

Las variables globales de tipo de clase están prohibidas, pero las variables globales de tipos integrados están permitidas. Por supuesto, las variables globales no constantes en código multiproceso también están prohibidas. Nunca utilice valores de retorno de funciones para inicializar variables globales.
Desafortunadamente, el orden en el que se llaman los constructores, destructores y operaciones de inicialización de las variables globales está sólo parcialmente especificado y puede cambiar en cada compilación, lo que genera errores difíciles de encontrar.
Por lo tanto, está prohibido utilizar variables globales de tipo clase (incluidas cadenas STL, vectores, etc.) porque su orden de inicialización puede causar problemas de construcción. Se pueden utilizar tipos integrados y estructuras sin constructores compuestos de tipos integrados. Si debe utilizar variables globales de tipo de clase, utilice el patrón singleton.
Para constantes de cadena globales, use cadenas de estilo C en lugar de cadenas STL:
const char kFrogSays[] = "ribbet";
aunque se permite el uso de variables globales en el ámbito global, asegúrese de pensarlo dos veces antes de usarlas. La mayoría de las variables globales deben ser miembros de datos estáticos de la clase, ya sea definidos en un espacio de nombres sin nombre cuando se usan solo dentro de un archivo .cc, o usar asociaciones estáticas para limitar el alcance de la variable.
Recuerde, las variables miembro estáticas se consideran variables globales, por lo que no pueden ser de tipo clase.

2.6.Resumen

  1. El espacio de nombres sin nombre en .cc puede evitar conflictos de nombres, limitar el alcance y evitar el uso directo del mensaje de uso para contaminar el espacio de nombres;
  2. Las clases anidadas cumplen con el principio de uso local, pero no pueden declararse hacia adelante en otros archivos de encabezado y tratan de no ser públicas;
  3. Intente no utilizar funciones globales y variables globales, considere las restricciones de alcance y espacio de nombres e intente formar unidades de compilación separadas;
  4. No utilice tipos de clase (incluidos contenedores STL) para variables globales (incluidas variables miembro estáticas) en subprocesos múltiples para evitar errores causados ​​por comportamientos poco claros.
    Además de considerar la contaminación y la legibilidad de los nombres, el uso del alcance es principalmente para reducir el acoplamiento y mejorar la eficiencia de compilación y ejecución.

3. clase

La clase es la unidad de código básica en C++ y, naturalmente, se usa ampliamente. Esta sección enumera lo que se debe y no se debe hacer al escribir una clase.

3.1 Responsabilidades del Constructor

Solo se llevan a cabo inicializaciones triviales en el constructor; si es posible, utilice el método Init() para inicializar centralmente datos significativos (no triviales).
Definición: Realizar operaciones de inicialización en el constructor.
Ventajas: composición tipográfica conveniente, no hay necesidad de preocuparse por si la clase está inicializada.
Desventajas: los problemas causados ​​​​al realizar operaciones en el constructor incluyen:

  1. Los errores no se informan fácilmente en los constructores y no se pueden utilizar excepciones.
  2. La falla de la operación hará que el objeto no se inicialice, lo que provocará un estado incierto.
  3. Si se llama a una función virtual dentro del constructor, la llamada no se enviará a la implementación de la subclase. Incluso si no hay una implementación de subclase actualmente, seguirá siendo un peligro oculto en el futuro.
  4. Si alguien creara una variable global de este tipo (aunque en contra de las reglas mencionadas en la sección anterior), se llamaría al constructor antes de main(), rompiendo potencialmente las suposiciones implícitas en el constructor. Por ejemplo, gflags aún no se ha inicializado.
    Conclusión: si el objeto requiere una inicialización significativa (no trivial), considere usar un método Init() adicional y/o agregar un indicador de miembro para indicar si el objeto se ha inicializado correctamente.

3.2.Constructores predeterminados

Si una clase define varias variables miembro y no tiene otros constructores, necesita definir un constructor predeterminado; de lo contrario, el compilador generará automáticamente un constructor predeterminado.
Definición: Cuando se crea un objeto sin parámetros, se llama al constructor predeterminado. Cuando se llama a new[] (para una matriz), siempre se llama al constructor predeterminado.
Ventajas: las estructuras se inicializan con valores "imposibles" de forma predeterminada, lo que facilita la depuración.
Desventaja: este es un trabajo redundante para el escritor del código.
Conclusión:
si la clase tiene variables miembro definidas y no se proporcionan otros constructores, debe definir un constructor predeterminado (sin parámetros). El constructor predeterminado es más adecuado para inicializar el objeto y hacer que el estado interno del objeto sea consistente y válido.
La razón para proporcionar un constructor predeterminado es: si no proporciona otros constructores y no define un constructor predeterminado, el compilador generará uno automáticamente para usted y el constructor generado por el compilador no inicializará el objeto.
Si la clase que define hereda una clase existente y no agrega nuevas variables miembro, no necesita definir un constructor predeterminado para la nueva clase.

3.3 Constructores explícitos

Utilice la palabra clave C++ explícita para constructores de un solo argumento.
Definición: por lo general, se puede usar un constructor con un solo parámetro para la conversión (nota del traductor: se refiere principalmente a la conversión implícita, ver más abajo), por ejemplo, se define Foo :: Foo (nombre de cadena) y cuándo se debe pasar en Cuando una función en un objeto Foo se pasa en una cadena, se llama al constructor Foo::Foo(nombre de cadena) y convierte la cadena en un objeto temporal Foo y la pasa a la función que llama. Parece conveniente, pero si no desea generar un nuevo objeto mediante la conversión, surgirán problemas. Para evitar la conversión implícita causada por la llamada del constructor, puede declararlo como explícito.
Ventajas: Evita cambios inoportunos.
Desventajas: Ninguna.
Conclusión:
todos los constructores de un solo argumento deben ser explícitos. En la definición de clase, agregue la palabra clave explícita antes del constructor de un solo argumento: explícito Foo(nombre de cadena);
Excepción: en algunos casos, no es necesario declarar explícito el constructor de copia; una clase que se usa intencionalmente como transparente contenedor para otras clases. Estas excepciones deberían indicarse claramente en los comentarios.

3.4.Copiar constructores

Utilice el constructor de copia sólo cuando necesite copiar un objeto de clase en su código; utilice DISALLOW_COPY_AND_ASSIGN cuando no sea necesario copiar.
Definición: el constructor de copia se puede utilizar al crear un nuevo objeto mediante copia (especialmente al pasar un objeto por valor).
Ventajas: el constructor de copias facilita la copia de objetos. Los contenedores STL requieren que todo el contenido se pueda copiar y asignar.
Desventajas: Las copias implícitas de objetos en C++ son la fuente de muchos errores y problemas de rendimiento. El constructor de copia reduce la legibilidad del código: en comparación con el paso por referencia, es más difícil rastrear los objetos pasados ​​por valor y el lugar donde se modifica el objeto se vuelve difícil de alcanzar.
Conclusión:
una gran cantidad de clases no necesitan ser copiables, ni necesitan un constructor de copia ni un operador de asignación. Desafortunadamente, si no los declara activamente, el compilador los generará automáticamente y los hará públicos.
Puede considerar agregar un constructor de copia ficticia y una operación de asignación a la parte privada de la clase, solo con declaraciones y sin definiciones. Debido a que estos programas vacíos se declaran privados, el compilador informará un error cuando otro código intente usarlos. Para mayor comodidad, puede usar la macro DISALLOW_COPY_AND_ASSIGN:
// Las macros que prohíben el uso de constructores de copia y operaciones de asignación
// deben usarse en privado: de la clase
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
TypeName(const TypeName&);
void operator= (const TypeName&)
clase Foo { público: Foo(int f); ~Foo(); privado:




DISALLOW_COPY_AND_ASSIGN(Foo);
};
Como se mencionó anteriormente, DISALLOW_COPY_AND_ASSIGN debe usarse en la mayoría de los casos. Si la clase realmente necesita ser copiable, el motivo debe explicarse en el archivo de encabezado de la clase, y el constructor de copia y la operación de asignación deben ser definido apropiadamente Nota Detectar situaciones de autoasignación en operator=.
Al utilizar una clase como contenedor STL, es posible que tenga la tentación de hacer que la clase se pueda copiar. En situaciones similares, lo que realmente deberías hacer es usar un puntero para señalar el objeto en el contenedor STL. Puedes considerar usar std::tr1::shared_ptr.

3.5 Estructuras y clases (Estructuras vs. Clases)

Use struct solo cuando solo haya datos y use class para todo lo demás.
En C++, las palabras clave estructura y clase tienen casi el mismo significado. Les agregamos semántica artificial para que podamos elegir razonablemente qué palabra clave usar para el tipo de datos definido.
Struct se usa en objetos pasivos que solo contienen datos, que pueden incluir constantes asociadas, pero no tienen funciones funcionales aparte de acceder a miembros de datos. La función de acceso se implementa mediante acceso directo sin llamadas a métodos. Aquí proporcionamos Los métodos mencionados son los que se usan únicamente para procesar miembros de datos, como constructores, destructores, Inicializar(), Reset() y Validar().
Si necesita más funciones, la clase es más adecuada, si no está seguro, utilice la clase directamente.
Si se combina con STL, puede usar struct en lugar de class para functores y rasgos.
Nota: Las variables miembro de clases y estructuras utilizan diferentes reglas de nomenclatura.

3.6 Herencia

Usar composición (composición, nota del traductor, esto también lo enfatiza repetidamente GoF en "Patrones de diseño") suele ser más apropiado que usar herencia. Si se usa herencia, solo use herencia pública.
Definición: cuando una subclase hereda una clase base, la subclase contiene las definiciones de todos los datos y operaciones de la clase base principal. En la práctica de C++, la herencia se utiliza principalmente en dos situaciones: herencia de implementación, donde la subclase hereda el código de implementación de la clase principal; herencia de interfaz, donde la subclase solo hereda los nombres de los métodos de la clase principal.
Ventajas: la implementación de la herencia reduce la cantidad de código al reutilizar el código de la clase base intacto. Debido a que la herencia es una declaración en tiempo de compilación, tanto los codificadores como los compiladores pueden comprender las operaciones y detectar errores. La herencia de interfaz se puede utilizar para mejorar mediante programación la funcionalidad de una API específica de una clase. El compilador también puede detectar errores cuando la clase no define la implementación necesaria de la API.
Desventajas: para la herencia de implementación, resulta más difícil comprender la implementación porque el código para implementar la subclase se extiende entre la clase principal y la subclase. Las subclases no pueden anular funciones no virtuales de la clase principal y, por supuesto, no pueden modificar su implementación. La clase base también puede definir algunos miembros de datos y también se debe distinguir el diseño físico de la clase base.
Conclusión:
toda la herencia debe ser pública. Si desea una herencia privada, debe incluir una instancia de clase base como miembro.
No utilice demasiado la herencia de implementación; la composición suele ser más apropiada. Intente usar la herencia solo si "es un" ("es-a", nota del traductor, use una combinación para otros casos "tiene-a"): si Bar es de hecho "un" tipo de Foo, entonces deje que Bar sea un subclase de Foo.
Si es necesario, haga que el destructor sea virtual. Necesidad significa que si la clase tiene una función virtual, su destructor debe ser una función virtual.
Nota del traductor: En cuanto al caso especial en el que la subclase no tiene miembros de datos adicionales, o incluso la clase principal no tiene ningún miembro de datos, si la llamada al destructor es necesaria es un debate semántico. Desde la perspectiva de las especificaciones de diseño de programación , En la clase principal que contiene funciones virtuales. En una clase, es absolutamente necesario definir un destructor virtual.
Las funciones de los miembros que están restringidas a subclases están protegidas. Cabe señalar que los miembros de datos siempre deben ser privados.
Al redefinir una función virtual derivada, declarela explícitamente como virtual en la clase derivada. Causa raíz: si se omite virtual, el lector debe recuperar todos los antepasados ​​de la clase para determinar si la función es una función virtual (Nota del traductor, aunque no afecta la naturaleza de que sea una función virtual).

3.7.Herencia múltiple

Hay muy pocas ocasiones en las que realmente se necesita la herencia de implementaciones múltiples. La herencia múltiple se usa solo cuando como máximo una clase base contiene una implementación y las otras clases base son clases de interfaz pura con el sufijo Interfaz.
Definición: La herencia múltiple permite que las subclases tengan múltiples clases base, es necesario distinguir entre clases base que son interfaces puras y clases base que tienen implementaciones.
Ventajas: la herencia de implementaciones múltiples le permite reutilizar más código que la herencia única.
Desventajas: hay muy pocas ocasiones en las que realmente se necesita la herencia de implementaciones múltiples. La herencia de implementaciones múltiples parece una buena solución y, por lo general, se pueden encontrar soluciones más claras, claras y diferentes.
Conclusión: La herencia múltiple sólo se puede utilizar si todas las superclases, excepto la primera, son interfaces puras. Para garantizar que sean interfaces puras, estas clases deben tener el sufijo Interface.
Nota: Con respecto a esta regla, existe una excepción en Windows (Nota del traductor, se explicará en las excepciones a la regla en el último artículo de esta traducción).

3.8.Interfaz

Las interfaces se refieren a clases que cumplen condiciones específicas. Estas clases tienen el sufijo Interfaz (no es obligatorio).
Definición: Cuando una clase cumple con los siguientes requisitos, se denomina interfaz pura:

  1. Sólo funciones virtuales puras ("=0") y funciones estáticas (excepto los destructores mencionados a continuación);
  2. No hay miembros de datos no estáticos;
  3. No se define ningún constructor. Si lo hay, no contiene parámetros y está protegido;
  4. Si es una subclase, solo puede heredar clases que cumplan las condiciones anteriores y tengan Interfaz como sufijo.
    No se puede crear una instancia de la clase de interfaz directamente porque declara funciones virtuales puras. Para garantizar que todas las implementaciones de una clase de interfaz se puedan destruir correctamente, se debe declarar un destructor virtual para ella (como excepción a la regla 1, el destructor no puede ser una función virtual pura). Para obtener detalles específicos, consulte la Sección 12.4 de "El lenguaje de programación C++, tercera edición" de Stroustrup.
    Ventajas: el uso del sufijo de interfaz permite a otros saber que no pueden agregar funciones de implementación o miembros de datos no estáticos a la clase de interfaz, lo que es especialmente importante para la herencia múltiple. Además, para los programadores de Java, el concepto de interfaz ya está profundamente arraigado en el corazón de la gente.
    Desventajas: el sufijo de la interfaz aumenta la longitud del nombre de la clase, lo que resulta inconveniente para la lectura y la comprensión. Al mismo tiempo, las características de la interfaz no deben exponerse a los clientes como detalles de implementación.
    en conclusión:. Solo cuando se satisfacen las necesidades anteriores, la clase termina con Interface, pero a la inversa, la clase que satisface las necesidades anteriores no necesariamente termina con Interface.

3.9 Sobrecarga del operador

Excepto en algunas circunstancias específicas, no sobrecargue a los operadores.
Definición: una clase puede definir operadores como +, /, etc., para que puedan usarse directamente como tipos integrados.
Ventajas: hace que el código parezca más intuitivo, al igual que los tipos integrados (como int), los operadores sobrecargados hacen que los nombres de funciones aburridos como Equals(), Add(), etc. sean mucho más interesantes. Para que algunas funciones de plantilla funcionen correctamente, es posible que necesite definir operadores.
Desventajas: aunque la sobrecarga del operador hace que el código sea más intuitivo, también tiene algunas deficiencias.

  1. Confunde su intuición y le hace pensar que algunas operaciones que requieren mucho tiempo son tan livianas como las operaciones integradas;
  2. Es más difícil encontrar el sitio de llamada del operador sobrecargado. Encontrar Equals() es obviamente mucho más fácil que la llamada equivalente ==;
  3. Algunos operadores pueden operar con punteros, lo que puede generar errores fácilmente. Foo + 4 hace una cosa, mientras que &Foo + 4 puede hacer otra completamente diferente. Para ambos, el compilador no informará un error, lo que dificulta la depuración;
  4. La sobrecarga también tiene efectos secundarios que pueden sorprenderle. Por ejemplo, las clases que sobrecargan al operador & no se pueden declarar hacia adelante.
    Conclusión:
    En general, no sobrecargue a los operadores, especialmente la operación de asignación (operador =) es más insidiosa, por lo que se debe evitar la sobrecarga. Si es necesario, puede definir funciones como Equals(), CopyFrom(), etc.
    Sin embargo, hay casos raros en los que es necesario sobrecargar los operadores para poder interactuar con plantillas o clases C++ "estándar" (como operator<<(ostream&, const T&)). Esto es aceptable si está justificado, pero debe evitarlo. si es posible de esta manera. En particular, no sobrecargue al operador solo para usarlo como clave en el contenedor STL.u operador <, en su lugar, debe crear tipos de funtores de comparación de tamaño e igualdad al declarar el contenedor.
    Algunos algoritmos STL requieren la sobrecarga del operador
    Puede hacer esto al hacerlo y no olvide documentar el motivo.
    Consulte constructor de copias y sobrecarga de funciones.

3.10.Control de Acceso

Privatizar los miembros de datos y proporcionar funciones de acceso relacionadas, como definir la variable foo_ y la función de valor foo(), y la función de asignación set_foo().
La definición de funciones de acceso suele estar integrada en el archivo de encabezado.
Consulte herencia y denominación de funciones.

3.11.Orden de Declaración

Utilice un orden de declaración específico en una clase: público: antes de privado:, funciones miembro antes de miembros de datos (variables).
El orden de definición es el siguiente: público:, protegido:, privado: Si esa parte no está disponible, simplemente ignórala.
Dentro de cada bloque, el orden de declaración es generalmente el siguiente:

  1. typedefs y enums;
  2. constante;
  3. Constructor;
  4. incinerador de basuras;
  5. Funciones miembro, incluidas funciones miembro estáticas;
  6. Miembros de datos, incluidos miembros de datos estáticos.
    La macro DISALLOW_COPY_AND_ASSIGN se coloca después del bloque privado: como última parte de la clase. Ver constructor de copias.
    La definición de funciones en el archivo .cc debe ser lo más coherente posible con el orden de declaración.
    No incluya funciones grandes en la definición de clase. Por lo general, solo las funciones cortas que no tienen un significado especial o requisitos de alto rendimiento se definen como funciones en línea. Para obtener más detalles, consulte las funciones en línea en el primer artículo de la traducción.

3.12 Escribir funciones cortas

Prefiere funciones breves y concisas.
Las funciones largas a veces son apropiadas, por lo que no existe un límite estricto en la longitud de la función. Si la función supera las 40 líneas, considere dividirla sin afectar la estructura del programa.
Incluso si una función larga funciona muy bien ahora, una vez que alguien la modifica, pueden surgir nuevos problemas o incluso provocar errores difíciles de encontrar. Mantenga las funciones lo más breves y simples posible para que a otros les resulte más fácil leer y modificar el código.
Mientras trabaja en su código, puede encontrar funciones largas y complejas. No tenga miedo de modificar el código existente: si resulta difícil de usar, depurar o necesita usar una pequeña parte, considere dividirlo en partes más pequeñas. Piezas más manejables, varias funciones.

3.13.Resumen

  1. No hagas demasiadas inicializaciones relacionadas lógicamente en el constructor;
  2. El constructor predeterminado proporcionado por el compilador no inicializa las variables. Si se definen otros constructores, el compilador ya no los proporcionará y el codificador debe proporcionar un constructor predeterminado por sí mismo;
  3. Para evitar la conversión implícita, el constructor de un solo parámetro debe declararse explícito;
  4. Para evitar el abuso de constructores de copia y operaciones de asignación y generación automática por parte del compilador, actualmente puede declararlos como privados y no es necesario implementarlos;
  5. Utilice struct solo como recopilación de datos;
  6. Composición > Herencia de implementación > Herencia de interfaz > Herencia privada, las funciones virtuales sobrecargadas por subclases también deben declarar la palabra clave virtual, aunque el compilador no lo permite;
  7. Evite el uso de herencia múltiple. Al usarlo, excepto una clase base que contiene implementación, las otras clases base son interfaces puras;
  8. El nombre de la clase de interfaz tiene el sufijo Interfaz. Excepto por proporcionar destructores virtuales implementados y funciones miembro estáticas, las demás son funciones virtuales puras. No define miembros de datos no estáticos y no proporciona constructores. Si se proporcionan, declararlo como protegido;
  9. Para reducir la complejidad, intente no sobrecargar a los operadores y proporcione documentación cuando se utilice en plantillas y clases estándar;
  10. Las funciones de acceso generalmente están integradas en los archivos de encabezado;
  11. Orden de declaración: público->protegido->privado;
  12. El cuerpo funcional debe ser lo más corto y compacto posible, con una única función.

4. Experiencia de Google

Google tiene muchas técnicas y funciones autoimplementadas que hacen que el código C++ sea más robusto, así como formas de utilizar C++ que son diferentes a las de otros lugares.

4.1. Punteros inteligentes

Si realmente necesita utilizar punteros inteligentes, Scoped_ptr es totalmente capaz. En casos muy especiales, como para objetos en contenedores STL, solo debes usar std::tr1::shared_ptr, y nunca usar auto_ptr bajo ninguna circunstancia.
Los punteros "inteligentes" parecen punteros, pero en realidad son objetos con semántica adicional. Tomando Scoped_ptr como ejemplo, cuando se destruye Scoped_ptr, el objeto al que apunta se elimina. Lo mismo ocurre conshared_ptr, yshared_ptr implementa el recuento de referencias para que el puntero solo se elimine cuando se destruya el último objeto al que apunta.
En términos generales, tendemos a diseñar código con una afiliación de objeto clara. La afiliación de objeto más clara es usar el objeto directamente como un campo o variable local sin usar punteros en absoluto. El otro extremo es que el puntero contado por referencias no pertenece a ningún objeto. El problema con este diseño es que puede conducir fácilmente a referencias circulares u otras condiciones extrañas que impiden que el objeto se elimine, e incluso las operaciones atómicas serán muy lento durante cada copia o tarea.
Aunque no se recomienda, hay ocasiones en las que un puntero contado por referencias es la solución más sencilla y eficaz.

4.2.CPPLint

Utilice cpplint.py para detectar errores de estilo.
Cpplint.py es una herramienta que lee archivos fuente e identifica errores de estilo. Aunque no es perfecto y tiene muchas ventajas y desventajas, sigue siendo una herramienta útil. Los mensajes de error activos se pueden ignorar colocando //NOLINT al final de la línea.
Algunos proyectos vienen con instrucciones sobre cómo ejecutar cpplint.py desde las herramientas del proyecto. Si no, puedes descargarlo por separado.
Nota del traductor: Parece que lo que Google llama la diferencia es tratar de evitar el uso de punteros inteligentes :D, intentar estar localizado al usarlos y anteponer la seguridad.

5. Otras características de C++

5.1 Argumentos de referencia

Por lo tanto, los parámetros pasados ​​por referencia deben agregarse con const.
Definición: en lenguaje C, si una función necesita modificar el valor de una variable, el parámetro formal debe ser un puntero, como int foo (int *pval). En C++, las funciones también pueden declarar parámetros de referencia: int foo(int &val).
Ventajas: definir parámetros formales como referencias evita códigos feos como (*pval)++. También es necesario para aplicaciones como constructores de copias y, a diferencia de los punteros, no acepta punteros nulos NULL.
Desventajas: es fácil causar malentendidos, porque la referencia es sintácticamente un valor pero tiene la semántica de un puntero.
Conclusión:
en la lista de parámetros de la función, todas las referencias deben ser constantes:
void Foo(const string &in, string out);
De hecho, esta es una convención estricta: los parámetros de entrada son valores o referencias constantes, y los parámetros de salida son punteros; los parámetros de entrada pueden ser punteros constantes, pero no se pueden utilizar parámetros de referencia no constantes.
Cuando se enfatiza que los parámetros no se copian y deben existir durante toda la vida del objeto, se pueden usar punteros constantes. Es mejor especificarlos en los comentarios. Los adaptadores STL como bind2nd y mem_fun no aceptan parámetros de referencia, en cuyo caso la función también debe declararse con parámetros de puntero.
5.2 Sobrecarga de funciones
Utilice únicamente funciones sobrecargadas (incluidos los constructores) cuando los tipos de parámetros de entrada sean diferentes y las funciones sean las mismas. No utilice la sobrecarga de funciones para imitar los parámetros de funciones predeterminados.
Definición: puede definir un tipo de parámetro de función como const string& y definir su tipo de función sobrecargada como const char
.
clase MiClase { público: void Analizar (cadena constante &texto);


void Analyze(const char *text, size_t textlen);
};
Ventajas: al sobrecargar funciones del mismo nombre con diferentes parámetros, el código es más intuitivo, el código con plantilla debe sobrecargarse y brinda comodidad a los visitantes.
Desventajas: una razón para limitar el uso de la sobrecarga es que es difícil determinar qué función se llama en una llamada específica. Otra razón es que cuando una clase derivada solo sobrecarga algunas variables de la función, muchas personas se confundirán acerca de la semántica de herencia. . Además, al leer el código del cliente de la biblioteca, los parámetros de función predeterminados causan confusión innecesaria.
Conclusión: si desea sobrecargar una función, considere dejar que el nombre de la función contenga información de parámetros; por ejemplo, use AppendString(), AppendInt() en lugar de Append().

5.3.Argumentos predeterminados

Deshabilite las funciones para que no utilicen parámetros predeterminados.
Ventajas: una función a menudo se usa con una gran cantidad de valores predeterminados, y estos valores ocasionalmente se anulan. Los parámetros predeterminados brindan la conveniencia de definir menos funciones para excepciones raramente involucradas.
Desventajas: La gente suele mirar el código existente para determinar cómo usar una API. Los parámetros predeterminados dificultan copiar y pegar el código anterior para presentar todos los parámetros. Puede causar problemas importantes cuando los parámetros predeterminados no se aplican al código nuevo.
Conclusión: todos los parámetros deben especificarse claramente, lo que obliga a los programadores a considerar la API y los valores de cada parámetro pasado, y evitar el uso de parámetros predeterminados que los programadores quizás no conozcan.

5.4 Matrices de longitud variable y alloca()

Están prohibidos los arreglos de longitud variable y alloca().
Ventajas: las matrices de longitud variable tienen una sintaxis natural y las matrices de longitud variable y alloca() también son muy eficientes.
Desventajas: las matrices de longitud variable y alloca() no son parte del C++ estándar y, lo que es más importante, asignan tamaños en función de los datos de la pila (pila), lo que puede provocar pérdidas de memoria difíciles de encontrar: "Funciona bien en mi máquina, pero murió inexplicablemente cuando llegó al producto”.
Conclusión: utilice un asignador seguro como Scoped_ptr/scoped_array.

5.5.Amigos

Se permite el uso razonable de clases y funciones de amigos.
Los amigos generalmente se definen en el mismo archivo para evitar que los lectores vayan a otros archivos para encontrar miembros privados de una clase. Un lugar donde se utilizan a menudo los amigos es declarar a FooBuilder como amigo de Foo, de modo que FooBuilder pueda construir correctamente el estado interno de Foo sin exponer ese estado. En algunos casos, es conveniente declarar una clase de prueba unitaria como amiga de la clase bajo prueba.
Los amigos amplían (pero no rompen) los límites de encapsulación de una clase. Cuando desea permitir que sólo otra clase acceda a un miembro, normalmente es mejor utilizar amigos que declararlo como público. Por supuesto, la mayoría de las clases sólo deberían proporcionar miembros públicos con quienes interactuar.

5.6.Excepciones

No utilice excepciones de C++.
ventaja:

  1. Las excepciones permiten que las aplicaciones de nivel superior decidan cómo manejar fallas "imposibles" que ocurren en funciones anidadas subyacentes, a diferencia de los registros de códigos de error que son lo más oscuros y complicados posible;
  2. Utilizada en muchos otros lenguajes modernos, la introducción de excepciones hace que C++ sea más compatible con Python, Java y otros lenguajes similares a C++;
  3. Muchas bibliotecas de terceros de C++ utilizan excepciones y desactivarlas dificultará su combinación;
  4. Las excepciones son la única solución al fallo del constructor. Aunque las excepciones se pueden simular mediante la función de fábrica o el método Init(), requieren una asignación de montón o un nuevo estado "ilegal", respectivamente;
  5. Las excepciones son realmente útiles en un marco de prueba.
    defecto:
  6. Al agregar una declaración de lanzamiento a una función existente, se deben verificar todos los sitios de llamada, e incluso si tienen al menos protección de seguridad de excepción básica, o el programa finaliza normalmente, la excepción nunca se puede detectar. Por ejemplo: si f() llama a g() llama a h(), h arroja una excepción detectada por f, g debe tener cuidado para evitar una limpieza incompleta;
  7. En términos simples, las excepciones harán que el flujo de control del programa (flujo de control) no se pueda determinar mirando el código: la función puede regresar a un lugar incierto, lo que dificulta la administración y la depuración del código. Por supuesto, puede especificar cuándo , dónde y cómo usarlo. Las excepciones se utilizan para minimizar los gastos generales, pero imponen a los desarrolladores la carga de dominar estas regulaciones;
  8. La seguridad excepcional requiere RAII y diferentes prácticas de codificación. Escribir código seguro para excepciones de forma fácil y correcta requiere mucho soporte. Permitir excepciones;
  9. Agregar excepciones aumentará el tamaño del código de ejecución binaria, aumentará el tiempo de compilación (puede que no tenga mucho impacto) y también puede aumentar la presión sobre el espacio de direcciones;
  10. La utilidad de las excepciones puede incentivar a los desarrolladores a lanzar excepciones en momentos inapropiados o a recuperarse de excepciones en lugares inseguros, por ejemplo, donde la entrada ilegal del usuario puede provocar que se lance una excepción. Permitir excepciones haría que una guía de estilo de programación como esta fuera mucho más larga.
    Conclusión:
    A primera vista, las ventajas de utilizar excepciones superan las desventajas, especialmente en proyectos nuevos. Sin embargo, para el código existente, la introducción de excepciones implicará todo el código dependiente. Si se permite el uso de excepciones en nuevos proyectos, también será problemático al integrar con código que no usaba excepciones antes. Debido a que la mayor parte del código C++ existente de Google no tiene manejo de excepciones, introducir código nuevo con manejo de excepciones es bastante difícil.
    Dado que el código existente de Google no acepta excepciones, usar excepciones en el código existente es más costoso que usarlas en proyectos nuevos y el proceso de migración será más lento y propenso a errores. Tampoco creemos que las alternativas válidas a las excepciones, como códigos de error, aserciones, etc., sean seriamente gravosas.
    No nos oponemos al uso de excepciones por motivos filosóficos o morales, sino por motivos prácticos. Porque esperamos usar proyectos de código abierto en Google, pero usar excepciones en el proyecto causará inconvenientes, porque también recomendamos no usar excepciones en proyectos de código abierto en Google. Obviamente, no es realista si necesitamos derribar estos proyectos y comenzar. terminado. .
    Hay una excepción a esto para el código de Windows (espera hasta la última publicación :D).
    Nota del traductor: El manejo de excepciones obviamente no es algo que pueda explicarse claramente en unas pocas oraciones. Tome el constructor como ejemplo. Muchos libros de C++ mencionan que solo se pueden manejar excepciones cuando la construcción falla. Google prohíbe el uso de excepciones. Para su propia conveniencia. Para decirlo sin rodeos, solo se basa en el costo de la administración del software. En el uso real, usted aún toma su propia decisión.

5.7 Información de tipo de tiempo de ejecución (RTTI)

Prohibimos el uso de RTTI.
Definición: RTTI permite a los programadores identificar el tipo de objetos de clase C++ en tiempo de ejecución.
Ventajas: RTTI es muy útil en determinadas pruebas unitarias, como cuando se prueban clases de fábrica para comprobar si un objeto recién creado es del tipo dinámico esperado.
Raramente usado excepto para pruebas.
Desventajas: Identificar tipos en tiempo de ejecución significa que hay un problema con el diseño en sí. Si necesita determinar el tipo de un objeto en tiempo de ejecución, esto generalmente significa que necesita repensar el diseño de su clase.
Conclusión: No utilice RTTI excepto para pruebas unitarias. Si descubre que el código que necesita escribir se comporta de manera diferente según el tipo de objeto, considere otra forma de identificar el tipo de objeto.
Las funciones virtuales pueden ejecutar diferentes códigos según el tipo de subclase y el trabajo se deja en manos del objeto mismo.
Si el trabajo se realiza en código fuera del objeto, considere un esquema de despacho dual, como el patrón Visitante, que facilita determinar el tipo de clase fuera del objeto mismo.
Si cree que no puede dominar el método anterior, puede usar RTTI, pero piénselo dos veces y no implemente manualmente una solución que se parezca a RTTI (solución alternativa similar a RTTI). Nos oponemos al uso de RTTI y También nos oponemos a la aparente herencia de clases con etiquetas tipográficas.

5.8 Conversión de tipo (Casting)

Utilice conversiones de tipo C++ como static_cast<>(). No utilice int y = (int)x o int y = int(x).
Definición: C++ introduce diferentes tipos de operaciones de conversión de tipos que son diferentes de C.
Ventajas: El problema con la conversión de tipos en lenguaje C es que la operación de Shandong Haihua es relativamente ambigua: a veces realiza una conversión forzada (como (int) 3.5), a veces realiza una conversión de tipo (como (int) "hola " "). Además, la búsqueda de conversión de tipos de C++ es más fácil y llamativa.
Desventajas: la sintaxis es desagradable.
Conclusión: utilice el estilo C++ en lugar de las versiones de estilo C.

  1. static_cast: similar a la conversión de estilo C, puede realizar coerción de valor o conversión ascendente explícita de un puntero de una clase principal a una subclase;
  2. const_cast: elimina atributos constantes;
  3. reinterpret_cast: conversión insegura entre tipos de punteros y punteros enteros u otros, úselo solo cuando sepa todo lo que está haciendo;
  4. Dynamic_cast: no lo use excepto para pruebas. Excepto para pruebas unitarias, si necesita determinar información de tipo en tiempo de ejecución, el diseño es defectuoso (consulte RTTI).

5.9 Corrientes

Utilice únicamente transmisiones al iniciar sesión.
Definición: Las secuencias reemplazan a printf() y scanf().
Ventajas: con las secuencias, no necesita preocuparse por el tipo de objeto al generar, y no tiene que preocuparse de que la cadena de formato no coincida con la lista de parámetros (aunque este problema no existe cuando se usa printf en gcc ) Al abrir y cerrar el archivo correspondiente, la secuencia puede construirse y destruirse automáticamente.
Desventajas: las secuencias dificultan la ejecución de funciones funcionales como pread (). Si no usa funciones como printf pero usa secuencias, es difícil operar el formato (especialmente la cadena de formato comúnmente utilizada %.*s). no admite caracteres Reordenación del operador de cadena (%1s), que es útil para la internacionalización.
Conclusión: no utilice transmisiones a menos que lo requiera la interfaz de registro; en su lugar, utilice printf o similar. Hay muchos pros y contras del uso de secuencias, la coherencia del código lo supera todo, no utilice secuencias en su código.
Discusión ampliada:
existe cierto debate sobre esta regla, y aquí están las razones más profundas. Recuerde el principio de unicidad (solo un sentido): queremos usar solo un determinado tipo de E/S en cualquier momento para que el código sea consistente en todas las E/S. Por lo tanto, no queremos que el usuario decida si usar flujos o printf + lectura/escritura, debemos decidir qué método usar. La razón para hacer de los registros una excepción es que las transmisiones son muy adecuadas para esto y existen razones históricas para ello.
Los defensores del streaming argumentan que el streaming es la opción obvia, pero su argumento no es claro ni sólido: todas las ventajas que señalan son también sus desventajas. La mayor ventaja de las transmisiones es que no necesita preocuparse por el tipo de objeto de salida al generar. Esto es un punto destacado, pero también una desventaja: es fácil usar el tipo incorrecto y el compilador no le avisará. . Un tipo de error que se produce fácilmente cuando se utilizan secuencias es:
cout << this; // Imprime la dirección
cout << *this; // Imprime el contenido.
El compilador no informará un error porque << está sobrecargado. Debido a this we Se desaconseja el uso de la sobrecarga de operadores.
Algunas personas dicen que el formato de printf es feo e ilegible, pero las transmisiones no son mucho mejores. Eche un vistazo a los siguientes dos fragmentos de código: ¿cuál es más legible?
cerr << “Error al conectar con '" << foo->bar()->hostname.first
<< “:” << foo->bar()->hostname.segundo << ": " << strerror(errno );

fprintf(stderr, “Error al conectarse a '%s:%u: %s”,
foo->bar()->hostname.first, foo->bar()->hostname.segundo,
strerror(errno));
usted Podrías decir: "Sería mejor encapsular la transmisión." Eso está bien aquí, pero ¿qué pasa con otros lugares? Y no lo olvides, el objetivo es hacer que el lenguaje sea lo más pequeño posible, no agregar cosas nuevas que otros necesiten aprender.
Cada método tiene sus pros y sus contras. "No hay mejor, sólo mejor". El dogma simplista nos advierte que debemos elegir uno de ellos. La decisión final mayoritaria es printf + lectura/escritura.

5.10 Preincremento y Predecremento

Utilice la forma con prefijo (++i) de los operadores de incremento y decremento para iteradores y otros objetos de plantilla.
Definición: Cuando el valor de la expresión no se usa después de que la variable se incrementa (++i o i++) o disminuye (–i o i–), es necesario determinar si se usa aumento y disminución con prefijo o posfijo.
Ventajas: Independientemente del valor de retorno, el incremento previo (++i) suele ser más eficiente que el incremento posterior (–i), porque el incremento posterior y el decremento requieren una copia del valor i de la expresión. , si i es un iterador u otro tipo no numérico, el costo de copiar es relativamente alto. Dado que los dos métodos de incremento automático tienen la misma acción, ¿por qué no utilizar el incremento previo directamente?
Desventajas: En lenguaje C, cuando no se usa el valor de una expresión, el enfoque tradicional es usar post-incremento, especialmente en bucles for. Algunas personas piensan que el post-incremento es más fácil de entender porque es como el lenguaje natural. (i) viene antes del verbo predicado (++).
Conclusión: para valores simples (no objetos), no importa de ninguna manera. Para iteradores y tipos de plantillas, use incrementos (decrementos) prefijados.

5.11.Uso de constante

Le recomendamos encarecidamente que utilice const siempre que sea posible.
Definición: agregue la palabra clave const antes de la variable o parámetro declarado para indicar que el valor de la variable no se puede modificar (como const int foo) Agregue la calificación const a la función en la clase para indicar que la función no modificará el estado de la variable miembro de la clase (como class Foo { int Bar(char c) const; };).
Ventajas: es más fácil para las personas comprender cómo se utilizan las variables y los editores pueden realizar una mejor verificación de tipos y una mejor generación de código. Las personas se sienten más seguras al escribir código correcto porque saben que las funciones que llaman están restringidas en el sentido de que pueden o no modificar los valores de las variables. Incluso en la programación multiproceso sin bloqueos, la gente sabe qué funciones son seguras.
Desventajas: si pasa una variable constante a una función, el prototipo de la función también debe ser constante (de lo contrario, la variable necesita conversión de tipo const_cast), lo cual es especialmente problemático al llamar a funciones de biblioteca.
Conclusión: las variables constantes, los miembros de datos, las funciones y los parámetros agregan una capa de protección a la detección de tipos en tiempo de compilación y detectan mejor los errores lo antes posible. Por lo tanto, recomendamos encarecidamente utilizar const siempre que sea posible:

  1. Si la función no modifica los parámetros de referencia pasados ​​o de tipo de puntero, dichos parámetros deben ser constantes;
  2. Declare funciones como constantes siempre que sea posible. Las funciones de acceso siempre deben ser constantes. Otras funciones que no modifican ningún miembro de datos también deben ser constantes. No llame a funciones no constantes y no devuelva punteros o referencias no constantes a miembros de datos;
  3. Si un miembro de datos no cambia después de construir el objeto, se puede definir como constante.
    Sin embargo, no abuses de const. Por ejemplo, const int * const * const x; es un poco excesivo. Incluso si describe con precisión x, puedes escribirlo como const int** x.
    Se puede usar la palabra clave mutable, pero no es seguro en subprocesos múltiples. Al usarla, primero debe considerar la seguridad del subproceso.
    posición const:
    Algunas personas prefieren la forma de int const foo a const int foo. Piensan que la primera es más consistente y por lo tanto más legible: sigue el principio de que const siempre se ubica después del objeto que describe (int). Sin embargo, el principio de coherencia no se aplica aquí y la autoridad de "no abusar" contrarresta el uso coherente. Poner const primero es más fácil de leer, porque en el lenguaje natural el adjetivo (const) va antes del sustantivo (int).
    Esto significa que recomendamos poner const primero, no un requisito, ¡pero debemos tener en cuenta la coherencia del código!

5.12.Tipos de números enteros

Entre los tipos de enteros integrados en C++, el único que se usa es int. Si se necesitan variables de diferentes tamaños en el programa, puede usar el tipo entero de ancho preciso en <stdint.h>, como int16_t.
Definición: C ++ no especifica el tamaño de los tipos de números enteros. Generalmente, la gente piensa que short es de 16 bits, int es de 32 bits, long es de 32 bits y long long es de 64 bits.
Ventajas: Mantiene las declaraciones consistentes.
Desventajas: los tamaños de enteros en C++ varían entre compiladores y arquitecturas.
Conclusión:
<stdint.h> define int16_t, uint32_t, int64_t y otros tipos de enteros. Cuando necesite determinar el tamaño del tipo de entero, puede usarlos en lugar de short, long sin signo, long, etc. Entre los tipos de enteros de C, sólo se utiliza int. Cuando corresponda, se recomienda utilizar tipos estándar como size_t y ptrdiff_t.
El más utilizado es el int ordinario para números enteros, que normalmente no son demasiado grandes, como el conteo de bucles. Puedes pensar en int como al menos 32 bits, pero no creas que será más de 32 bits. Si necesitas un entero de 64 bits, puedes usar int64_t o uint64_t.
Para números enteros grandes, utilice int64_t.
No utilice enteros sin signo como uint32_t a menos que represente un patrón de bits en lugar de un valor. No utilice tipos sin signo incluso si el valor no será negativo; utilice aserciones para proteger los datos.
Tipos enteros sin signo:
algunas personas, incluidos algunos autores de libros de texto, recomiendan utilizar tipos sin signo para representar números no negativos, donde el tipo indica la forma del valor numérico. Sin embargo, en C, esta ventaja se ve superada por los errores que provoca. Eche un vistazo:
for (unsigned int i = foo.Length()-1; i >= 0; --i)…
¡El código anterior nunca termina! A veces, gcc encontrará el error y le alertará, pero normalmente no. También aparecerán errores similares al comparar variables calificadas y variables sin firmar, principalmente porque el mecanismo de promoción de tipos de C (esquema de promoción de tipos, la relación de conversión de promoción entre varios tipos integrados en el lenguaje C) hará que los tipos sin firmar se comporten de forma inesperada.
Por lo tanto, utilice aserciones para declarar que las variables no sean negativas y no utilice tipos sin signo.

Portabilidad de 5.13.64 bits

En principio, el código debería ser más amigable tanto en sistemas de 64 bits como de 32 bits, especialmente para salida, comparación y alineación de estructuras:

  1. Algunos tipos especificados por printf() no son muy portátiles en sistemas de 32 y 64 bits, y el estándar C99 define algunos formatos portátiles. Desafortunadamente, MSVC 7.1 no los admite todos y faltan algunos en el estándar. Entonces, a veces tenemos que definir la versión fea nosotros mismos (usar el estilo estándar incluye el archivo inttypes.h):
    // printf macros para size_t, en el estilo de inttypes.h
    #ifdef _LP64
    #define __PRIS_PREFIX "z"
    #else
    #define __PRIS_PREFIX
    #endif

// Utilice estas macros después de % en una cadena con formato printf
// para obtener el comportamiento correcto de 32/64 bits, como este:
// size_t size = records.size();
// printf(“%“PRIuS”\n”, tamaño);

#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS PRIS_PREFIX "o"
tipo no usar usar comentarios
nulos (u otro tipo de puntero) %lx % p
int64_t %qd, %lld %"PRId64"
uint64_t %qu, %llu, %llx %"PRIu64", %"PRIx64"
size_t %u %"PRIuS", %"PRIxS" Especificación C99 %zu
ptrdiff_t %d % " PRIdS" C99 especifica %zd.
Tenga en cuenta que el compilador expandirá la macro PRI a una cadena independiente. Por lo tanto, si utiliza una cadena de formato no constante, deberá insertar el valor de la macro en el formato en lugar de el nombre de la macro. Lo mismo ocurre cuando se utiliza la macro PRI* Longitud y se puede especificar otra información después de %. Por ejemplo, printf("x = %30"PRIuS"\n", x) se expandirá a printf("x = %30" "u" "\n", x) en Linux de 32 bits y el compilador manejará es printf(“x = %30u\n”, x).
2) Recuerde sizeof(void *) != sizeof(int), si necesita un número entero del tamaño de un puntero, use intptr_t.
3) Se debe tener cuidado con la alineación de la estructura, especialmente para las estructuras almacenadas en disco. En sistemas de 64 bits, cualquier clase/estructura con miembros int64_t/uint64_t se procesará como alineación de 8 bytes de forma predeterminada. Si el código de 32 y 64 bits comparte estructuras en el disco, debe asegurarse de que las estructuras estén alineadas de manera consistente en ambas arquitecturas. La mayoría de los compiladores proporcionan una forma de ajustar la alineación de las estructuras. __attribute ((packed)) se puede usar en gcc
, y MSVC proporciona #pragma pack() y __declspec(align()).
4) Utilice LL o ULL como sufijo al crear constantes de 64 bits, como por ejemplo:
int64_t my_value = 0x123456789LL;
uint64_t my_mask = 3ULL << 48;
5) Si realmente necesita que los sistemas de 32 y 64 bits tengan códigos diferentes, puede preceder al uso de la variable de código. (Intente no hacer esto e intente realizar modificaciones locales cuando los use).

5.14 Macros de preprocesador

Tenga cuidado al utilizar macros e intente utilizar funciones, enumeraciones y constantes en línea.
Las macros significan que usted y el compilador ven el código de manera diferente, lo que puede provocar un comportamiento inesperado, especialmente si la macro existe en el ámbito global.
Afortunadamente, en C++ las macros no son tan necesarias como en C. El código crítico para la eficiencia en línea de macros (código crítico para el rendimiento) se puede reemplazar por funciones en línea; las constantes almacenadas en macros se pueden reemplazar por variables constantes; los nombres de variables largos "abreviados" de las macros se pueden reemplazar por referencias; use macros para la compilación condicional, esto. .., es mejor no hacerlo. Hacerlo hará que las pruebas sean más dolorosas (con la excepción de #define para evitar que se vuelvan a incluir archivos de encabezado).
Las macros pueden hacer algunas cosas que otras tecnologías no pueden lograr. En algunas bibliotecas de código (especialmente en bibliotecas de bajo nivel), puede ver ciertas características de las macros (como cadena (Nota del traductor, use #), concatenación, Nota del traductor, utilizar ##), etc.). Pero antes de usarlo, considere cuidadosamente si puede lograr el mismo efecto sin usar macros.
Nota del traductor: para aplicaciones avanzadas de macros, consulte "Aplicaciones avanzadas de macros en lenguaje C".
Los patrones de uso que se indican a continuación pueden evitar algunos problemas al utilizar macros y sirven como referencia al utilizar macros:

  1. No defina macros en archivos .h;
  2. #define correctamente antes de su uso, #undef correctamente después de su uso;
  3. No simplemente #undef una macro existente, elija un nombre que no entre en conflicto;
  4. No utilice macros que puedan causar construcciones de C++ desequilibradas (Nota del traductor), al menos documente su comportamiento.
    5.15.0 y NULL (0 y NULL)
    Utilice 0 para números enteros, 0,0 para números reales, NULL para punteros y '\0' para caracteres (cadenas).
    No hay duda de que 0 se utiliza para números enteros y 0,0 para números reales.
    Para punteros (valores de dirección), ya sea que se use 0 o NULL, Bjarne Stroustrup recomienda usar el 0 original. Recomendamos usar NULL, que parece un puntero. De hecho, algunos compiladores de C++ (como gcc 4.1.0) proporcionan específicamente La definición de NULL puede dar advertencias útiles, especialmente cuando sizeof(NULL) y sizeof(0) no son iguales.
    Utilice '\0' para caracteres (cadenas), que no solo tiene el tipo correcto sino que también tiene buena legibilidad.

5.16.tamaño de

Utilice sizeof(varname) en lugar de sizeof(type) siempre que sea posible.
Sizeof(varname) se utiliza porque el código se sincroniza automáticamente cuando cambia el tipo de variable. En algunos casos, sizeof(type) puede tener sentido, pero debe evitarse tanto como sea posible. Si el tipo de variable cambia, no se puede sincronizar .
Datos de estructura;
memset(&data, 0, tamaño de(datos));
memset(&data, 0, tamaño de(Estructura));

5.17.Biblioteca Boost (Impulso)

Utilice únicamente bibliotecas aprobadas en Boost.
Definición: La biblioteca Boost es una biblioteca C++ de código abierto, gratuita, revisada por pares y muy popular.
Ventajas: la calidad del código Boost es generalmente mayor, la portabilidad es buena y llena muchos vacíos en la biblioteca estándar de C++, como rasgos de tipo, carpetas más completas, mejores punteros inteligentes y también proporciona la implementación de TR1 (una extensión de la biblioteca estándar). ).
Desventajas: algunas bibliotecas de Boost promueven una mala legibilidad de las prácticas de programación, como la metaprogramación y otras técnicas de plantillas avanzadas, y un estilo de programación demasiado "funcional".
Conclusión: Para proporcionar una mejor legibilidad a quienes leen y mantienen el código, solo permitimos el uso de un subconjunto maduro de funciones de Boost. Actualmente, estas bibliotecas incluyen:

  1. Par comprimido: boost/compressed_pair.hpp;
  2. Contenedor de puntero: boost/ptr_container no incluye ptr_array.hpp ni serialización.
    Consideraremos activamente agregar funciones de Boost siempre que sea posible, para que no tengas que ceñirte a esta regla.

5.18.Resumen

  1. Para los punteros inteligentes, la seguridad es lo primero, la conveniencia es lo segundo y debe estar lo más localizado posible (scoped_ptr);
  2. El parámetro de referencia es constante; de ​​lo contrario, se utiliza el parámetro de puntero;
  3. El uso de la sobrecarga de funciones debe ser claro y fácil de leer;
  4. En vista del fácil uso indebido, el uso de parámetros de función predeterminados está prohibido (cuestionable);
  5. Está prohibido el uso de matrices de longitud variable;
  6. Uso razonable de amigos;
  7. Para facilitar la gestión del código, se prohíbe el uso de excepciones (discutible);
  8. Está prohibido utilizar RTTI; de lo contrario, rediseñe el código;
  9. Utilice la conversión de tipos de estilo C++ y no utilice Dynamic_cast excepto para pruebas unitarias;
  10. Utilice stream o printf + lectura/escritura, es un problema;
  11. Se puede utilizar un incremento/disminución previo, pero no se requiere un incremento/disminución posterior;
  12. Utilice const si se puede utilizar y abogue por const primero;
  13. Utilice tipos enteros de cierto tamaño y no utilice tipos sin signo excepto bytes;
  14. Al formatear la salida y la alineación de la estructura, preste atención a las diferencias del sistema entre sistemas de 32 y 64 bits;
  15. Intente evitar el uso de macros excepto para la cadena y la concatenación;
  16. Utilice 0 para números enteros, 0,0 para números reales, NULL para punteros y '\0' para caracteres (cadenas);
  17. Utilice sizeof(varname) en lugar de sizeof(type);
  18. Utilice únicamente bibliotecas aprobadas en Boost.

6. Convención de nomenclatura

La regla de coherencia más importante es la gestión de nombres. El estilo de nombres puede determinar directamente si la entidad nombrada es: tipo, variable, función, constante, macro, etc., sin buscar declaraciones de entidad. El motor de coincidencia de patrones en nuestro cerebro se basa en estas reglas de nomenclatura.
Las reglas para nombrar son algo arbitrarias, pero la coherencia es más importante que nombrar según las preferencias personales, así que no importa lo que pienses, las reglas son reglas.

6.1 Reglas generales de denominación

Los nombres de funciones, variables y archivos deben ser descriptivos y no demasiado abreviados. Los tipos y variables deben ser sustantivos, y los nombres de funciones pueden usar verbos "imperativos".
Cómo nombrar:
proporcione un nombre descriptivo tanto como sea posible, no ahorre espacio, es más importante permitir que otros comprendan su código rápidamente, buenas opciones de nombres:
int num_errors; // Bueno.
int num_completed_connections; // Bueno.
Usar nombres vagos para nombres feos, abreviaturas o caracteres aleatorios:
int n; // Malo - sin sentido.
int nerr; // Malo - abreviatura ambigua.
int n_comp_conns; // Malo - abreviatura ambigua.
Los tipos y nombres de variables son generalmente sustantivos: como FileOpener , núm_errores.
Los nombres de las funciones suelen ser directivos, como OpenFile(), set_num_errors(). Las funciones de acceso deben describirse con más detalle y deben coincidir con las variables a las que acceden.
Abreviaturas:
no utilice abreviaturas a menos que sean obvias fuera del proyecto, por ejemplo:
// Bueno
// Muestran nombres propios sin abreviaturas.
int num_dns_connections; // La mayoría de la gente sabe lo que significa “DNS”.
​​int price_count_reader; / / OK, el precio cuenta. Tiene sentido.

// ¡Malo!
// Las abreviaturas pueden resultar confusas o ambiguas fuera de un grupo pequeño.
int wgc_conexiones; // Sólo tu grupo sabe lo que esto significa.
int pc_lector; // Muchas cosas se pueden abreviar como “pc”.
不要用省略字母的缩写:
int error_count; // Bien.
int error_cnt; // Malo.

6.2.Nombres de archivos

El nombre del archivo debe estar todo en minúsculas y puede contener guiones bajos (_) o guiones (-), según la convención del proyecto.
Nombres de archivos aceptables:
my_useful_class.cc
my-useful-class.cc
myusefulclass.cc
Los archivos C++ terminan con .cc y los archivos de encabezado terminan con .h.
No utilice nombres de archivos que ya existan en /usr/include (Nota del traductor, para UNIX, Linux y otros sistemas), como db.h.
En general, intente hacer que el nombre del archivo sea más claro: http_server_logs.h es mejor que logs.h. Al definir una clase, el nombre del archivo generalmente aparece en pares, como foo_bar.h y foo_bar.cc, correspondientes a la clase FooBar.
Las funciones en línea deben colocarse en el archivo .h. Si la función en línea es corta, colóquela directamente en el archivo .h. Si el código es relativamente largo, se puede colocar en un archivo que termine en -inl.h. Para las clases que contienen mucho código en línea, puede haber tres archivos:
url_table.h // La declaración de clase.
url_table.cc // La definición de clase.
url_table-inl.h // Funciones en línea que incluyen mucho código.
Consulte el primera sección del archivo Chapter-inl.h.

6.3.Nombres de tipo

Cada palabra en el nombre del tipo comienza con una letra mayúscula y no contiene guiones bajos: MyExcitingClass, MyExcitingEnum.
Todos los nombres de tipos (clases, estructuras, typedefs, enumeraciones) utilizan la misma convención, por ejemplo:
// clases y estructuras
class UrlTable { …
class UrlTableTester { …
struct UrlTableProperties { …

// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// enumeraciones
enumeración UrlTableErrors {…

6.4 Nombres de variables

Los nombres de las variables siempre están en minúsculas, las palabras están conectadas con guiones bajos y las variables de los miembros de la clase terminan con guiones bajos, como my_exciting_local_variable, my_exciting_member_variable_.
Denominación de variables ordinarias:
Ejemplo:
cadena nombre_tabla; // OK - usa guión bajo.
cadena nombre_tabla; // OK - todo en minúsculas.
cadena nombre_tabla; // Malo - mayúsculas y minúsculas mixtas.
Miembros de datos de clase:
Los miembros de datos de la estructura pueden ser los mismos como variables ordinarias, no es necesario terminar con un guión bajo como una clase:
struct UrlTableProperties { string name; int num_entries; } Para una discusión sobre estructuras y clases, consulte la sección "Estructuras versus clases" en el tercer artículo. Variables globales: no existen requisitos especiales para las variables globales. Úselas con moderación. Pueden tener el prefijo g_ u otros signos que sean fáciles de distinguir de las variables locales.





6.5 Nombres de constantes

Anteponga el nombre con k: kDaysInAWeek.
Todas las constantes en tiempo de compilación (ya sean locales, globales o dentro de una clase) siguen siendo ligeramente diferentes de otras variables, k va seguida de una palabra que comienza con una letra mayúscula: const int kDaysInAWeek = 7
;

6.6 Nombres de funciones

Las funciones regulares (funciones regulares, nota del traductor, aquí a diferencia de funciones especiales como funciones de acceso) tienen mayúsculas y minúsculas mixtas, mientras que los descriptores de acceso y mutadores deben coincidir con el nombre de la variable: MyExcitingFunction(), MyExcitingMethod(), my_exciting_member_variable(), set_my_exciting_member_variable( ).
Función ordinaria:
el nombre de la función comienza con una letra mayúscula, la primera letra de cada palabra está en mayúscula y no hay subrayado:
AddTableEntry()
DeleteUrl()
Función de acceso:
la función de acceso debe coincidir con el nombre de la variable a la que se accede. Aquí hay un extracto de una variable de instancia num_entries_ Class:
class MyClass { public: ... int num_entries() const { return num_entries_; } void set_num_entries(int num_entries) { num_entries_ = num_entries; }



private:
int num_entries_;
};
Otros nombres cortos de funciones en línea también pueden usar letras minúsculas. Por ejemplo, llamar a una función de este tipo en un bucle ni siquiera necesita almacenar en caché su valor, y los nombres en minúsculas son aceptables.
Nota del traductor: A partir de este punto se puede ver que el nombre de la función en minúsculas significa que se puede usar directamente en línea.

6.7 Nombres de espacios de nombres

El nombre del espacio de nombres está todo en minúsculas y se denomina según el nombre del proyecto y la estructura del directorio: google_awesome_project.
Para obtener información sobre los espacios de nombres y cómo nombrarlos, consulte la Parte 2: Espacios de nombres.

6.8 Nombres de enumeradores

Los valores de enumeración deben estar en letras mayúsculas, con las palabras separadas por guiones bajos: MY_EXCITING_ENUM_VALUE.
El nombre de la enumeración es de tipo, por lo tanto, mayúsculas y minúsculas: UrlTableErrors.
enumerar UrlTableErrors { OK = 0, ERROR_OUT_OF_MEMORY, ERROR_MALFORMED_INPUT, };



6.9 Nombres de macros

No vas a utilizar macros, ¿verdad? Si se usa, así: MY_MACRO_THAT_SCARES_SMALL_CHILDREN.
Consulte el cuarto capítulo de macros de preprocesamiento. Las macros generalmente no se utilizan. Si son absolutamente necesarias, sus nombres deben estar en letras mayúsculas y subrayados como nombres de enumeración: #define
ROUND(x) …
#define PI_ROUNDED 3.0

6.10.Excepciones a las reglas de denominación

Al nombrar objetos similares a entidades C/C++ existentes, puede consultar las convenciones de nomenclatura existentes:
bigopen()
nombre de función, consulte open()
uint
typedef definición de tipo estructura o clase
bigpos
, consulte pos
sparse_hash_map
STL entidades similares; consulte STL nombrar la constante
LONGLONG_MAX convencional
, similar a INT_MAX

6.11.Resumen

  1. Regla general: no abrevie a voluntad. Si es excusable escribir ChangeLocalValue como ChgLocVal, escribir ModifyPlayerName como MdfPlyNm es demasiado. Excepto para los nombres de funciones que pueden ser verbos apropiados, intente usar sustantivos claros y fáciles de entender para otros nombres, 2. Macros
    , use todo en mayúsculas + guiones bajos para enumeraciones, etc.;
  2. Utilice todas las letras minúsculas + guión bajo para variables (incluidas las variables miembro de clase y estructura), archivos, espacios de nombres, funciones de acceso, etc. Las variables miembro de clase terminan con un guión bajo y las variables globales comienzan con g_;
  3. Las funciones ordinarias, tipos (incluidas clases, estructuras, tipos de enumeración), constantes, etc. utilizan mayúsculas y minúsculas mixtas, sin guiones bajos;
  4. Haga referencia a convenciones de nomenclatura existentes o similares.

7. Comentarios

Aunque los comentarios son dolorosos de escribir, son cruciales para garantizar la legibilidad del código. Las siguientes reglas describen qué comentarios se deben hacer y dónde deben estar. Por supuesto, recuerde que los comentarios son realmente importantes, pero el mejor código es el autodocumentado. Es mucho mejor tener nombres claros de tipos y variables que explicar nombres ambiguos mediante comentarios.
Los comentarios están escritos para otra persona (la siguiente persona que necesita entender su código), así que sea serio, ¡esa próxima persona podría ser usted!

7.1.Estilo de comentario

Utilice // o /* / para unificar.
Se puede usar // o / */, // simplemente se usa más ampliamente para garantizar la uniformidad en la forma de comentar y el estilo de los comentarios.

7.2 Comentarios de archivo

Agregue un aviso de derechos de autor al comienzo de cada archivo, seguido de una descripción del contenido del archivo.
Avisos legales e información del autor:
Cada documento contiene los siguientes elementos, en orden:

  1. Copyright (declaración de derechos de autor): como Copyright 2008 Google Inc.;
  2. Versión de licencia (licencia estándar): elija la versión de licencia adecuada para el proyecto, como Apache 2.0, BSD, LGPL, GPL;
  3. Autor (línea de autor): Identifica el autor original del archivo.
    Si realiza cambios importantes en un archivo creado por otra persona, agregue su información a la información del autor para que otros sepan a quién contactar si tienen preguntas sobre el archivo.
    Contenido del archivo:
    después del permiso de derechos de autor de cada archivo y la información del autor, se debe proporcionar un comentario que describa el contenido del archivo.
    Por lo general, el archivo .h debe brindar una breve descripción de las funciones y el uso de la clase declarada, y el archivo .cc contiene más detalles de implementación o discusiones sobre algoritmos. Si cree que estos detalles de implementación o discusiones sobre algoritmos son útiles para leer, puede puede poner .Los comentarios en cc se colocan en .h e indican en .cc que el documento está en .h.
    No copie simplemente comentarios entre .h y .cc, ya que los comentarios copiados se desviarán del significado real.

7.3 Comentarios de clase

Cada definición de clase va acompañada de comentarios que describen la funcionalidad y el uso de la clase.
// Itera sobre el contenido de una GargantuanTable. Ejemplo de uso:
// GargantuanTable_Iterator* iter = table->NewIterator();
// for (iter->Seek(“foo”); !iter->done(); iter- >Next()) { // proceso(iter->key(), iter->value()); // } // eliminar iter; class GargantuanTable_Iterator { }; Si cree que esto se ha descrito en detalle en en la parte superior del archivo Clase, si desea simplemente decir "ver la parte superior del archivo para obtener una descripción completa", debe agregar algunos comentarios en la clase. Si la clase tiene alguna suposición de sincronización, documéntela. Si varios subprocesos pueden acceder a instancias de esta clase, asegúrese de prestar atención a la documentación cuando las utilice.







7.4 Comentarios de función

Los comentarios en la declaración de función describen la función y la definición de función describe la implementación de la función.
Declaración de función:
se coloca un comentario antes de la declaración para describir la función y el uso. El comentario utiliza una descripción ("Abre el archivo") en lugar de una instrucción ("Abre el archivo"); el comentario es solo para describir la función en lugar de que decirle a la función qué hacer. Por lo general, los comentarios no describen cómo se implementa la función, eso es parte de la definición.
El contenido de la anotación en la declaración de función:

  1. entradas (entrada) y salidas (salida);
  2. Para funciones miembro de clase: si el objeto necesita mantener parámetros de referencia durante la llamada a la función y si estos parámetros se liberarán;
  3. Si la función asigna espacio, la persona que llama debe liberarlo;
  4. Si el parámetro puede ser NULL;
  5. ¿Existe alguna implicación en el rendimiento por el uso de la función?
  6. 如果函数是可重入的(reentrante),其同步前提(supuestos de sincronización)是什么?
    举例如下:
    // Devuelve un iterador para esta tabla. Es
    responsabilidad del cliente // eliminar el iterador cuando haya terminado con él,
    // y no debe utilizar el iterador una vez que
    se haya eliminado el objeto GargantuanTable // en el que se creó el iterador.
    //
    // El iterador se coloca inicialmente al principio de la tabla.
    //
    // Este método es equivalente a:
    // Iterador* iter = table->NewIterator();
    // iter->Buscar(“”);
    // devolver iter;
    // Si vas a buscar inmediatamente otro lugar en el
    // iterador devuelto, será más rápido usar NewIterator()
    // y evita la búsqueda adicional.
    Iterador* GetIterator() const;

Pero no incluya comentarios innecesarios, redundantes u obvios. No es necesario agregar "devuelve falso en caso contrario" al siguiente comentario, porque ya está implícito: //
Devuelve verdadero si la tabla no puede contener más entradas.
bool IsTableFull() ;

Al comentar constructores/destructores, recuerde que la persona que lee el código sabe cuál es el constructor/destructor, por lo que un comentario como "destruye este objeto" no tiene sentido. Explique qué hace el constructor con los parámetros (por ejemplo, si es el propietario del puntero) y qué limpia el destructor. Si es irrelevante, simplemente omita el comentario. Es normal que no haya ningún comentario antes del destructor.
Definición de función:
al definir cada función, se deben usar comentarios para explicar la función de la función y los puntos de implementación, como el hermoso código utilizado, los breves pasos de implementación, el motivo de dicha implementación y por qué es necesario bloquear la primera mitad, pero la segunda mitad no.
No copie comentarios directamente de la declaración de la función en el archivo .h o en otro lugar. Está bien describir brevemente lo que hace la función, pero la atención debe centrarse en cómo implementarla.

7.5 Comentarios variables

Generalmente el nombre de la variable en sí es suficiente para describir el propósito de la variable, en ciertos casos se necesitan comentarios adicionales.
Miembros de datos de clase:
cada miembro de datos de clase (también llamado variable de instancia o variable miembro) debe comentarse para explicar su propósito. Si la variable puede aceptar valores centinela como NULL o -1, debe explicarse, como por ejemplo: privado :
//
Realiza un seguimiento del número total de entradas en la tabla.
// Se utiliza para garantizar que no superemos el límite. -1 significa
// que aún no sabemos cuántas entradas tiene la tabla.
int num_total_entries_;
Variable global (constante):
similar a los miembros de datos, todas las variables globales (constantes) también deben comentarse con su significado y propósito, como por ejemplo:
// El número total de casos de prueba que ejecutamos en esta prueba de regresión.
const int kNumTestCases = 6;

7.6.Comentarios sobre la implementación

Comente lugares inteligentes, oscuros, interesantes e importantes en el código de implementación.
Comentarios antes del código:
Los comentarios deben agregarse antes de los bloques de código complejos o pendientes, como por ejemplo:
// Dividir el resultado entre dos, teniendo en cuenta que x
// contiene el acarreo del complemento.
for (int i = 0; i < result- >size(); i++) { x = (x << 8) + (*resultado)[i]; (*resultado)[i] = x >> 1; x &= 1; } Comentario de línea: bastante oscuro Si Si desea agregar un comentario al final de la línea, puede agregar un comentario al final de la línea con dos espacios después del código, como por ejemplo: // Si tenemos suficiente memoria, mapeemos también la porción de datos mmap_budget = max (0, mmap_budget - index_->length() ); if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock)) return; // Error ya registrado.









Observe que hay dos bloques de comentarios que describen este código, y los comentarios mencionan que se registraron errores cuando regresa la función.
Hay comentarios en las líneas adyacentes antes y después, que se pueden ajustar apropiadamente para hacerlas más legibles:
...
DoSomething(); // Comenta aquí para que los comentarios se alineen.
DoSomethingElseThatIsLonger(); // Comenta aquí para que haya dos espacios entre
// el código y el comentario.

NULL, verdadero/falso, 1, 2, 3...:
Al pasar un valor booleano o un número entero a la función, debes comentar el significado o usar constantes para que el código sea comprensible. Comparar:
bool Success = CalculateSomething( valor_interesante,
10,
falso,
NULL); // ¿Cuáles son estos argumentos?

Ejemplo:
bool Success = CalculateSomething(interesting_value,
10, // Valor base predeterminado.
false, // No es la primera vez que llamamos a esto.
NULL); // Sin devolución de llamada.

Utilice constantes o variables descriptivas:
const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool Success = CalculateSomething(interesting_value,
kDefaultBaseValue,
kFirstTimeCalling,
null_callback);

No:
tenga cuidado de nunca usar código de traducción de lenguaje natural como comentarios, asuma que la persona que lee su código es mejor que usted en C++ :D: //
Ahora revise la matriz b y asegúrese de que si ocurre i,
// el siguiente elemento es i +1.
… // Caray, qué comentario más inútil.

7.7 Puntuación, ortografía y gramática

Preste atención a la puntuación, la ortografía y la gramática; los comentarios bien escritos son mucho más fáciles de leer que los mal escritos.
Los comentarios son generalmente oraciones completas que contienen mayúsculas y puntos apropiados (.). Se puede hacer clic en los comentarios más cortos (como los comentarios al final de las líneas de código) como desee, pero aún debe prestar atención a la coherencia del estilo. Las oraciones completas son más legibles e indican que el comentario está completo en lugar de una idea a medias.
Aunque es un poco vergonzoso usar una coma cuando alguien señala que se debe usar un punto y coma. El código claro y legible sigue siendo importante, y la puntuación, la ortografía y la gramática adecuadas pueden ayudar con esto.

7.8.TODO Comentarios

Utilice comentarios TODO para soluciones temporales a corto plazo o código que sea lo suficientemente bueno pero no perfecto.
Dichos comentarios deben usar la cadena TODO en mayúsculas, seguida de su nombre, dirección de correo electrónico, etc. entre paréntesis (paréntesis), y también puede agregar dos puntos (dos puntos): el propósito es buscar de acuerdo con el formato TODO unificado: //
TODO ([email protected]): use un “*” aquí para el operador de concatenación.
// TODO(Zeke) cambie esto para usar relaciones.

Si lo agrega para "hacer algo algún día en el futuro", puede agregar una hora específica ("Reparar en noviembre de 2005") o un evento ("Eliminar este código cuando todos los clientes puedan manejar respuestas XML").

7.9.Resumen

  1. En cuanto al estilo de los comentarios, muchos programadores de C++ prefieren los comentarios de línea. Los codificadores de C aún pueden tener debilidad por los comentarios de bloque, o usar comentarios de bloque cuando comentan en secciones grandes de encabezados de archivos; 2. Los comentarios de archivos pueden mostrar sus logros. También es
    así otros pueden buscarte si causas problemas;
  2. Los comentarios deben ser concisos y directos, sin procrastinar ni redundantes. Se despreciará simplificar cosas complejas y complicar cosas simples;
  3. Para los programadores chinos, es un problema si comentar en inglés o en chino, pero pase lo que pase, los comentarios son para que otros los entiendan. ¿Es para mostrar su lengua materna o su dominio de un idioma extranjero además de los lenguajes de programación?
  4. Los comentarios no deben ser demasiado desordenados. Una sangría adecuada hará que la gente esté contenta de leerlos. Sin embargo, no es necesario especificar desde qué columna deben comenzar los comentarios (siempre me gusta esto cuando escribo código). En UNIX/LINUX, puedes también estoy de acuerdo en si usar tabulación o espacio, yo personalmente prefiero el espacio;
  5. TODO es muy bueno. A veces, los comentarios realmente se usan para marcar algunas áreas inacabadas o completadas de manera insatisfactoria. De esta manera, después de buscar, sabrá qué trabajo queda por hacer y se guardarán los registros.

8.Formato

El estilo y el formato del código son relativamente arbitrarios, pero es muy fácil para todos en un proyecto seguir el mismo estilo. Como individuo, es posible que no esté de acuerdo con cada parte de las siguientes reglas de formato, pero son importantes para todo el proyecto. obedecer a un estilo de programación unificado, lo que facilita que todos lean y comprendan el código.

8.1.Longitud de línea

El número de caracteres en cada línea de código no debe exceder los 80.
También reconocemos que esta regla es controvertida, pero con tanto código siguiéndola, creemos que la coherencia es más importante.
Ventajas: Los defensores de este principio piensan que es bárbaro obligarlos a cambiar el tamaño de la ventana de su editor. Muchas personas abren varias ventanas una al lado de la otra al mismo tiempo y no hay espacio adicional para ampliar una determinada ventana. La gente limita el tamaño máximo de la ventana y usa constantemente 80 columnas de ancho. ¿Por qué deberíamos cambiarlo?
Desventajas: Quienes se oponen a este principio argumentan que las líneas de código más anchas son más fáciles de leer y que el límite de 80 columnas es un defecto arcaico de las computadoras centrales de la década de 1960; los dispositivos modernos tienen pantallas más anchas y pueden mostrar fácilmente más código.
Conclusión: 80 caracteres es el máximo. excepción:

  1. Si una línea de comentarios contiene un comando o URL que excede los 80 caracteres, puede exceder los 80 caracteres para la conveniencia de copiar y pegar;
  2. Aquellos que contienen caminos largos pueden exceder las 80 columnas, así que trate de evitarlos;
  3. La protección del archivo de encabezado (evitando la inclusión repetida del primer artículo) puede ignorar este principio.

8.2.Caracteres no ASCII

Trate de no utilizar caracteres que no sean ASCII. Cuando los utilice, debe utilizar el formato UTF-8.
El texto de la interfaz de usuario no debe estar codificado en el código fuente, incluso si está en inglés, por lo que los caracteres que no sean ASCII deben usarse con moderación. Dichos caracteres se pueden incluir apropiadamente en casos especiales. Por ejemplo, cuando el código analiza archivos de datos externos, las cadenas no ASCII utilizadas como delimitadores en los archivos de datos se pueden codificar apropiadamente; más comúnmente, el código de prueba unitaria (que no requiere localización ) puede contener cadenas que no sean ASCII. En tales casos, se debe utilizar UTF-8 ya que su codificación puede ser entendida y procesada por muchas herramientas, al igual que la codificación hexadecimal, especialmente si mejora la legibilidad, como en "\xEF\xBB\ xBF" es un Unicode de ancho cero sin -carácter de espacio de ruptura que es invisible cuando se incluye en un archivo fuente en formato UTF-8.

8.3 Espacios frente a tabulaciones

Utilice sólo espacios y aplique sangría de 2 espacios a la vez.
Utilice espacios para la sangría, no utilice tabulaciones en su código y configure su editor para convertir tabulaciones en espacios.

8.4 Declaraciones y definiciones de funciones

El tipo de retorno va en la misma línea que el nombre de la función y, si corresponde, los parámetros.
La función se ve así:
ReturnType ClassName::FunctionName(Type par_name1, Type par_name2) { DoSomething(); } Si hay demasiado texto en la misma línea para acomodar todos los parámetros: ReturnType ClassName::ReallyLongFunctionName(Type par_name1, Type par_name2, Escriba par_name3) { DoSomething(); } Incluso el primer parámetro no cabe: ReturnType LongClassName::ReallyReallyReallyLongFunctionName( Escriba par_name1, // sangría de 4 espacios Escriba par_name2, escriba par_name3) { DoSomething(); // sangría de 2 espacios } Tenga en cuenta los siguientes puntos:


















  1. El valor de retorno siempre está en la misma línea que el nombre de la función;
  2. El paréntesis izquierdo (paréntesis abierto) siempre está en la misma línea que el nombre de la función;
  3. No hay espacio entre el nombre de la función y el paréntesis izquierdo;
  4. No hay espacios entre paréntesis y parámetros;
  5. La llave abierta siempre está al final de la misma línea que el último parámetro;
  6. La llave cerrada siempre se encuentra sola en la última línea de una función;
  7. Siempre hay un espacio entre el paréntesis de cierre y la llave de apertura;
  8. Todos los nombres de parámetros formales en la declaración e implementación de la función deben ser consistentes;
  9. Todos los parámetros formales deberían estar lo más alineados posible;
  10. La sangría predeterminada es de 2 espacios;
  11. Los parámetros empaquetados de forma independiente permanecen con una sangría de 4 espacios.
    Si la función es constante, la palabra clave const debe estar en la misma línea que el último parámetro.
    // Todo en esta firma de función cabe en una sola línea
    ReturnType FunctionName(Type par) const { }

// Esta firma de función requiere varias líneas, pero
// la palabra clave const está en la línea con el último parámetro
ReturnType ReallyLongFunctionName(Type par1,
Type par2) const { } Si algunos parámetros no se utilizan, cambie los parámetros en la función definición Anotar el nombre: // Siempre tenga parámetros con nombre en las interfaces class Shape { public: virtual void Rotate(doble radianes) = 0; }







// Siempre tenga parámetros con nombre en la declaración.
círculo de clase: forma pública { público: vacío virtual Rotar (radianes dobles); }


// Comentar los parámetros con nombre no utilizados en las definiciones.
void Circle::Rotate(double / radians /) {}
// Malo: si alguien quiere implementarlo más tarde, no está claro qué
// significa la variable.
Círculo vacío::Rotar(doble) {}

8.5 Llamadas a funciones

Intente ponerlos en la misma línea; de lo contrario, incluya los parámetros reales entre paréntesis.
Las llamadas a funciones siguen la siguiente forma:
bool retval = DoSomething(argumento1, argumento2, argumento3);

Si la misma línea no cabe, se puede dividir en varias líneas. Cada línea posterior debe alinearse con el primer parámetro real. No deje un espacio después del paréntesis izquierdo y antes del paréntesis derecho: bool retval = DoSomething(averyveryverylongargument1
,
argument2 , argumento3);
si hay muchos parámetros de función. Para facilitar la lectura, puede colocar solo un parámetro en cada línea:
bool retval = DoSomething(argumento1,
argumento2,
argumento3,
argumento4);
si el nombre de la función es demasiado largo y excede la longitud máxima de línea, puede colocar todos los parámetros en líneas separadas:
if (…) { if (…) { DoSomethingThatRequiresALongFunctionName( very_long_argument1, // sangría de 4 espacios argumento2, argumento3, argumento4); }








8.6 Declaraciones condicionales (Condicionales)

También se recomienda no agregar espacios entre paréntesis y poner la palabra clave else en una nueva línea.
Hay dos formatos aceptables para declaraciones condicionales básicas, uno con espacios entre los paréntesis y la condición, y otro sin ellos.
El formato más común es el formato sin espacios, cualquier formato es aceptable, pero la coherencia es la clave. Si está modificando un archivo, consulte el formato actual; si está escribiendo código nuevo, consulte el formato de otros archivos en el directorio o proyecto. Si todavía tiene dudas, no agregue espacios.
if (condición) { // sin espacios dentro de paréntesis
... // sangría de 2 espacios.
} else { // El else va en la misma línea que la llave de cierre
....
}

Si prefiere espacios dentro de paréntesis:
if (condición) { // espacios dentro de paréntesis - raro
... // sangría de 2 espacios.
} else { // El else va en la misma línea que la llave de cierre
....
}

Tenga en cuenta que en todos los casos hay un espacio entre if y el paréntesis izquierdo, y también entre el paréntesis derecho y la llave izquierda (si se usa):
if(condición) // Malo: falta espacio después de IF.if
(condición) { // Malo: falta espacio antes de {.
if(condición){ // Doblemente malo.
if (condición) { // Bueno: espacio adecuado después de IF y antes de {.
Algunas declaraciones condicionales están escritas en la misma línea para mejorar la legibilidad. Utilice solo cuando la declaración es simple y no usa una cláusula else:
if (x == kFoo) devuelve nuevo Foo();
if (x == kBar) devuelve nueva Bar();
Si la declaración tiene una rama else, es no permitido:
// No permitido - declaración IF en una línea cuando hay una cláusula ELSE
if (x) DoThis();
else DoThat();
Por lo general, las declaraciones de una sola línea no necesitan usar llaves. Es comprensible si te gusta. Algunas personas también requieren que se use if. Llaves:
if (condición)
DoSomething(); // sangría de 2 espacios.

si (condición) { Hacer algo(); // sangría de 2 espacios. }

Pero si alguna rama de la declaración usa llaves, otras partes también deben usarlas:
// No permitido - rizado en IF pero no en ELSE
if (condición) { foo; } else bar;


// No permitido - rizado en ELSE pero no en IF
if (condición)
foo;
más { barra; }

// Se requieren llaves alrededor de IF y ELSE porque
// una de las cláusulas usaba llaves.
si (condición) { foo; } más { barra; }



8.7 Bucles y declaraciones de cambio

La declaración de cambio se puede dividir en bloques usando llaves; el cuerpo del bucle vacío debe usar {} o continuar.
El bloque de casos en la declaración de cambio puede o no usar llaves, dependiendo de su preferencia. Cuando lo use, siga las instrucciones a continuación.
Si hay un valor que no cumple con las condiciones de enumeración del caso, incluya siempre un valor predeterminado (si hay un valor de entrada que el caso no procesa, el compilador emitirá una alarma). Si el valor predeterminado nunca se ejecutará, simplemente puede usar afirmar:
switch (var) { caso 0: { // sangría de 2 espacios ... // interrupción de sangría de 4 espacios; } caso 1: { ... ruptura; } predeterminado: { afirmar(falso ); } } El cuerpo del bucle vacío debe usar {} o continuar en lugar de un simple punto y coma: while (condición) { // Repetir la prueba hasta que devuelva false. } for (int i = 0; i < kSomeNumber; ++ i) {} // Bueno: cuerpo vacío. while (condición) continúa; // Bueno: continuar indica que no hay lógica.


















mientras (condición); // Malo: parece parte del ciclo do/ while.

8.8 Punteros y expresiones de referencia

No debe haber espacios antes ni después del punto (.) o la flecha (->), y no debe haber espacios después de los operadores de puntero/dirección (*, &).
Los siguientes son ejemplos correctos de expresiones de puntero y referencia:
x = *p;
p = &x;
x = ry;
x = r->y;

Aviso:

  1. Al acceder a miembros, no hay espacios antes o después del punto o flecha;
  2. No hay espacio después del operador de puntero * o &.
    Al declarar parámetros o variables de puntero, el asterisco puede aparecer inmediatamente al lado del tipo o nombre de la variable:
    // Estos están bien, espacio delante.char
    *c;
    const string &str;

// Estos están bien, con espacio a continuación.
char* c; // ¡pero recuerda hacer “char* c, *d, *e, …;”!
const string& str;
char * c; // Malo: espacios en ambos lados of *
const string & str; // Malo: los espacios en ambos lados de &
deben ser al menos consistentes en el mismo archivo (nuevo o existente).

8.9 Expresiones booleanas

Si una expresión booleana excede el ancho de línea estándar (80 caracteres), los saltos de línea deben estar unificados.
En el siguiente ejemplo, el operador lógico AND (&&) siempre está ubicado al final de la línea:
if (this_one_thing > this_other_thing &&
a_third_thing == a_fourth_thing &&
still_another & last_one) { } Ambos operadores lógicos AND (&&) están ubicados en al final de la línea, que se puede considerar. Insertar paréntesis adicionales puede ser muy útil para mejorar la legibilidad si se usan apropiadamente.


8.10 Valores de retorno de funciones (Valores de retorno)

No utilice paréntesis en expresiones de retorno.
No utilice paréntesis cuando una función devuelve:
return x; // no return(x);

8.11 Inicialización de variables y matrices

Elija = o ().
Debes elegir entre los dos. Las siguientes formas son correctas:
int x = 3;
int x(3);
nombre de cadena(“Algún nombre”);
nombre de cadena = “Algún nombre”;

8.12 Directivas de preprocesador

No aplique sangría a las directivas de preprocesamiento, comience desde el principio de la línea.
Incluso si las directivas de preprocesamiento están dentro de un bloque de código con sangría, las directivas deben comenzar al principio de la línea.
// Bueno: directivas al comienzo de la línea
if (lopside_score) { #if DISASTER_PENDING // Correcto: comienza al comienzo de la línea DropEverything(); #endif BackToNormal(); } // Malo: directivas con sangría if (lopside_score) { #if DISASTER_PENDING // ¡Incorrecto! El “#if” debe estar al principio de la línea DropEverything(); #endif // ¡Incorrecto! No sangrar “#endif” BackToNormal(); }











8.13 Formato de clase

Los atributos declarados son públicos:, protegidos:, privados: en orden, con sangría de 1 espacio cada vez (Nota del traductor, ¿por qué no dos? Algunas personas abogan por lo privado primero, para que quede claro de un vistazo qué miembros de datos se declaran y algunas personas defienden que tiene sentido juntar variables y operaciones de acuerdo con relaciones lógicas :-)).
El formato básico de una declaración de clase (si no conoce las anotaciones de clase, consulte la sección Anotaciones de clase en la Parte 6) es el siguiente:
clase MiClase : pública OtraClase { público: // ¡Observe la sangría de 1 espacio! MiClase( ); // Sangría regular de 2 espacios. explícito MiClase(int var); ~MiClase() {}



anular alguna función();
anular alguna función que no haga nada() { }

void set_some_var(int var) { some_var_ = var; }
int alguna_var() const { return alguna_var_; }

privado:
bool AlgunaFunciónInternal();

int alguna_var_;
int alguna_otra_var_;
DISALLOW_COPY_AND_ASSIGN(MiClase);
};
注意:

  1. Por lo tanto, el nombre de la clase base debe colocarse en la misma línea que el nombre de la subclase tanto como sea posible dentro del límite de 80 columnas;
  2. Las palabras clave públicas:, protegidas:, privadas: deben tener sangría de 1 espacio (Nota del traductor, MSVC usa principalmente sangría de tabulación y estas tres palabras clave no tienen sangría);
  3. Excepto por la primera palabra clave (generalmente pública), deje una línea en blanco antes de otras palabras clave. Si la clase es relativamente pequeña, no es necesario que esté en blanco;
  4. No dejes una línea en blanco después de estas palabras clave;
  5. Lo público ocupa el primer lugar, seguido de lo protegido y lo privado;
  6. Para conocer el orden de declaración, consulte la sección "Orden de declaración" en la Parte 3.

8.14.Listas de inicializadores

Coloque la lista de inicialización del constructor en la misma línea o en varias líneas con sangría de cuatro espacios.
Dos formatos de lista de inicialización aceptables:
// Cuando todo cabe en una línea:
MyClass::MyClass(int var): some_var_(var), some_other_var_(var + 1) { o // Cuando requiere varias líneas, sangra 4 espacios, poniendo los dos puntos en // la primera línea inicializadora: MyClass::MyClass(int var) : some_var_(var), // sangría de 4 espacios some_other_var_(var + 1) { // alineado ... DoSomething(); ... }









8.15 Formato del espacio de nombres

El contenido del espacio de nombres no tiene sangría.
Los espacios de nombres no agregan niveles de sangría adicionales, por ejemplo:
espacio de nombres {

void foo() { // Correcto. Sin sangría adicional dentro del espacio de nombres.

}

} // espacio de nombres

No sangrar:
espacio de nombres {

// Equivocado. Sangrado cuando no debería estarlo.
vacío foo() { ... }

} // espacio de nombres

8.16 Espacios en blanco horizontales

水平留白的使用因地制宜。不要在行尾添加无谓的留白。
普通:
void f(bool b) { // Las llaves abiertas siempre deben tener un espacio delante de ellas.

intyo = 0; // Los puntos y coma normalmente no tienen espacios delante.
intx[] = {0}; // Los espacios entre llaves para la inicialización de la matriz son
int x[] = {0}; // opcional. Si los usas ¡ponlos de ambos lados!
// Espacios alrededor de los dos puntos en listas de herencia e inicializadores.
class Foo : public Bar { public: // Para implementaciones de funciones en línea, coloque espacios entre las llaves // y la implementación misma. Foo(int b): Bar(), baz_(b) {} // No hay espacios dentro de llaves vacías. Restablecer vacío() { baz_ = 0; } // Espacios que separan las llaves de la implementación.





Agregar espacios en blanco redundantes creará una carga adicional para que otros editen, así que no incluya espacios en blanco adicionales. Si está seguro de que se ha modificado una línea de código, elimine los espacios adicionales o elimínelos cuando limpie espacios específicamente (asegúrese de que nadie más los esté usando).
Bucles y declaraciones condicionales:
if (b) { // Espacio después de la palabra clave en condiciones y bucles.
} else { // Espacios alrededor de else.
}
while (test) {} // Generalmente no hay espacio dentro de los paréntesis.
switch (i ) { for (int i = 0; i < 5; ++i) { switch ( i ) { // Los bucles y las condiciones pueden tener espacios dentro de if ( test ) { // paréntesis, pero esto es raro. Sea coherente. for ( int i = 0; i < 5; ++i ) { for ( ; i < 5 ; ++i) { // Los bucles For siempre tienen un espacio después del … // punto y coma, y ​​pueden tener un espacio antes del / /punto y coma.switch (i) {








caso 1: // No hay espacio antes de dos puntos en un caso de cambio.

caso 2: ruptura; // Usa un espacio después de dos puntos si hay código después.
操作符:
x = 0; // Los operadores de asignación siempre tienen espacios
// alrededor.
x = -5; // No hay espacios que separen los operadores unarios y sus
++x; // argumentos.
si (x && !y)

v = w * x + y / z; // Los operadores binarios normalmente tienen espacios alrededor,
v = w x + y/z; // pero está bien eliminar espacios alrededor de los factores.
v = w * (x + z); // Los paréntesis no deben tener espacios dentro.
模板和转换:
vector x; // No hay espacios dentro del ángulo
y = static_cast<char
>(x); // corchetes (< y >), antes
// <, o entre >( en una conversión.
vector<char *> x; // Los espacios entre el tipo y el puntero están
// bien, pero sea coherente.
set<list > x; // C++ requiere un espacio en > > .set
< list > x; // Opcionalmente, puedes utilizar
// espaciado simétrico en < <.

8.17 Espacios en blanco verticales

Cuanto menos espacio en blanco vertical, mejor.
Esto no es sólo una regla sino una cuestión de principios: no utilice líneas en blanco a menos que sea absolutamente necesario. En particular: no deje más de 2 líneas en blanco entre dos definiciones de función, no tenga líneas en blanco al principio y al final del cuerpo de la función y no agregue líneas en blanco a voluntad en el cuerpo de la función.
El principio básico es: cuanto más código se pueda mostrar en la misma pantalla, más fácil será comprender el flujo de control del programa. Por supuesto, un bloque de código demasiado denso es tan antiestético como un bloque demasiado escaso, por lo que depende de su criterio, pero en general, menos es mejor.
No debe haber líneas en blanco al principio ni al final de la función:
void Function() {

// Líneas en blanco innecesarias antes y después

}
No debe haber líneas en blanco al principio ni al final del bloque de código:
while (condición) { // Línea en blanco innecesaria después

}
si (condición) {

// Línea en blanco innecesaria antes de
}

También es aceptable una línea en blanco entre los bloques if-else:
if (condición) { // Algunas líneas de código son demasiado pequeñas para pasar a otra función, // seguidas de una línea en blanco.

} else { // Otro bloque de código }

8.18.Resumen

  1. En principio, el ancho de fila no debería exceder las 80 columnas, lo que ocuparía toda la pantalla de 22 pulgadas, lo cual es injustificable;
  2. Intente no utilizar caracteres que no sean ASCII. Si los utiliza, consulte el formato UTF-8 (especialmente en UNIX/Linux, se pueden considerar caracteres anchos en Windows) y trate de no acoplar constantes de cadena en el código, como crear archivos de recursos separados. Esto ya no es sólo una cuestión de estilo;
  3. Use espacios incondicionalmente en UNIX/Linux, y no hay nada de malo en usar Tab en MSVC;
  4. Parámetros de funciones, condiciones lógicas y listas de inicialización: todos los parámetros y nombres de funciones se colocan en la misma línea o todos los parámetros se separan en líneas separadas;
  5. Además de las llaves de apertura de las definiciones de funciones que se pueden colocar al principio de la línea, las llaves de apertura que incluyen declaraciones de función/clase/estructura/enumeración y varias declaraciones se pueden colocar al final de la línea, y todas las llaves de cierre son en líneas separadas;
  6. No dejes espacios antes y después del operador ./->. No dejes espacios antes y después de */&. Solo un operador es suficiente. Puedes elegir izquierda o derecha según tu preferencia;
  7. Las directivas/espacios de nombres de preprocesamiento no usan sangría adicional, las clases/estructuras/enumeraciones/funciones/declaraciones usan sangría;
  8. El uso de = o () para la inicialización depende de las preferencias personales, solo manténgalo consistente;
  9. No agregue () para regresar;
  10. No abuses de los espacios en blanco horizontales/verticales. Haz que sea más fácil de leer.

9.Excepciones a las reglas

Las prácticas de codificación explicadas anteriormente son básicamente obligatorias, pero todas las buenas reglas permiten excepciones.

9.1 Código no conforme existente

Se puede tolerar el código existente que no se ajuste al estilo de programación establecido.
Cuando modifica código que usa otros estilos, no necesita usar las convenciones de esta guía para mantenerlo consistente con el estilo original del código. Si está preocupado, puede discutirlo con el autor original del código o con la persona a cargo actual. Recuerde, la coherencia incluye la coherencia original.

9.2.Código de Windows

Los programadores de Windows tienen sus propios hábitos de codificación, que se derivan principalmente de algunos archivos de encabezado de Windows y otros códigos de Microsoft. Queremos que cualquiera pueda leer su código, por eso tenemos una guía separada sobre codificación C++ para todas las plataformas.
Si ha estado utilizando el estilo de codificación de Windows, es necesario reiterar algunas pautas que quizás haya olvidado (Nota del traductor, por qué siento que me están lavando el cerebro :D):

  1. No utilice la notación húngara (como definir una variable entera como iNum), utilice las convenciones de nomenclatura de Google, incluido el uso de la extensión .cc para archivos fuente;
  2. Windows ha definido muchos sinónimos para los tipos integrados originales (Nota del traductor, esto también me disgusta), como DWORD, HANDLE, etc. Esto es completamente aceptable e incluso recomendable al llamar a la API de Windows, pero aun así deberías intentarlo. Los tipos originales de C++, por ejemplo, usan const TCHAR* en lugar de LPCTSTR;
  3. Al compilar con Microsoft Visual C++, establezca el nivel de advertencia en 3 o superior y trate todas las advertencias como errores;
  4. No use #pragma una vez; como protección de inclusión, use la protección de inclusión estándar de C++ e incluya la ruta del archivo protegido al nivel superior del árbol del proyecto (Nota del traductor, #include<prj_name/public/tools.h>);
  5. A menos que sea absolutamente necesario, no utilice extensiones no estándar como #pragma y __declspec. Se permite el uso de __declspec(dllimport) y __declspec(dllexport), pero se deben pasar macros como DLLIMPORT y DLLEXPORT para que otros puedan usarlas. códigos al compartir Es fácil abandonar estas extensiones.
    En Windows, sólo hay unas pocas reglas que ocasionalmente se pueden infringir:
  6. Por lo general, tenemos prohibido usar herencia múltiple, pero puede usar herencia múltiple cuando use clases COM y ATL/WTL. Para ejecutar clases COM o ATL/WTL y sus interfaces, puede usar herencia de implementación múltiple;
  7. Aunque no se deben usar excepciones en el código, las excepciones se usan ampliamente en ATL y algunos STL (incluido STL de Visual C ++). Cuando se usa ATL, se deben definir _ATL_NO_EXCEPTIONS para bloquear excepciones. Es necesario estudiar si las excepciones STL también están bloqueadas. si no está protegido, también puede habilitar las excepciones del compilador. Tenga en cuenta que esto es sólo para compilar STL. Aún así, no debe escribir código que contenga manejo de excepciones;
  8. Por lo general, cada archivo fuente de cada proyecto contiene un archivo de encabezado llamado StdAfx.h o precompile.h para facilitar la precompilación del archivo de encabezado. Para que el código sea fácil de compartir con otros proyectos, evite incluir explícitamente este archivo (precompile.cc Excepto ), utilice la opción del compilador /FI para incluir automáticamente;
  9. Los archivos de encabezado de recursos, generalmente llamados recurso.h y que contienen solo macros, no necesitan cumplir con esta guía de estilo.

9.3.Trabajo en equipo

Utilice el sentido común y sea coherente.
Al editar código, tómate un momento para mirar otro código en el proyecto y determinar su estilo. Si otro código usa espacios en declaraciones if, úsalos también. Si los comentarios están encerrados en un cuadro
con asteriscos ( ), se hace lo mismo: / ****************************** ** **

  • Algunos comentarios están aquí.
  • Puede haber muchas líneas
    *************************************/
    El objetivo de utilizar un estilo de programación La guía es proporcionar un Con estándares de codificación comunes, todos pueden concentrarse en implementar el contenido en lugar de la presentación. Hemos dado especificaciones de estilo globales, pero el estilo local también es muy importante. Si el nuevo código que agrega en un archivo está lejos del estilo del código original, esto destruirá la belleza general del archivo en sí y afectará la lectura. Así que trate de evitarlo. él.
    Bien, casi he escrito sobre el estilo de codificación. El código en sí es más interesante, ¡así que disfrútalo!

Supongo que te gusta

Origin blog.csdn.net/weixin_30197685/article/details/132507070
Recomendado
Clasificación