guide - ¿Cuáles son tus modismos de estilo de codificación C++ favoritos
google code style java (24)
Es útil poner nombres de funciones en una nueva línea, para que pueda grep como
grep -R ''^fun_name'' .
para ellos. He visto ese estilo usado para muchos proyectos de GNU y me gusta:
static void
fun_name (int a, int b) {
/* ... */
}
¿Cuáles son tus modismos de estilo de codificación de C ++ favoritos? Pregunto acerca de la tipografía de estilo o codificación, como dónde colocas las llaves, hay espacios después de las palabras clave, el tamaño de las sangrías, etc. Esto se opone a las mejores prácticas o requisitos, como eliminar las matrices con delete[]
.
Aquí hay un ejemplo de uno de mis favoritos: en los inicializadores de clase C ++, colocamos los separadores en la parte delantera de la línea, en lugar de en la parte posterior. Esto hace que sea más fácil mantenerlo actualizado. También significa que las diferencias de control de código fuente entre versiones son más limpias.
TextFileProcessor::
TextFileProcessor( class ConstStringFinder& theConstStringFinder )
: TextFileProcessor_Base( theConstStringFinder )
, m_ThreadHandle ( NULL )
, m_startNLSearch ( 0 )
, m_endNLSearch ( 0 )
, m_LineEndGetIdx ( 0 )
, m_LineEndPutIdx ( 0 )
, m_LineEnds ( new const void*[ sc_LineEndSize ] )
{
;
}
Normalmente me atengo a KNF descrito en * BSD STYLE (9)
Siempre retoco y edito lo siguiente:
- Líneas nuevas superfluas
- Sin nueva línea en EOF
Escriba cada método o argumento de función en una línea separada de manera que pueda ser comentada fácilmente.
int ReturnMaxValue(
int* inputList, /* the list of integer values from which to get the maximum */
long size, /* count of the number of integer values in inputList */
char* extraArgs /* additional arguments that a caller can provide. */
)
Me gusta alinear código / inicializaciones en ''columnas'' ... Resulta muy útil cuando edito con un editor capaz de modo ''columna'' y también me parece mucho más fácil de leer ...
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true }, // Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000, false }, // Comment 3
{NULL, 5, true }, // Comment 4
};
Por el contrario, el mismo código no sangrado y formateado como se muestra arriba ... (Un poco más difícil de leer para mis ojos)
int myVar = 1; // comment 1
int myLongerVar = 200; // comment 2
MyStruct arrayOfMyStruct[] =
{
// Name, timeout, valid
{"A string", 1000, true},// Comment 1
{"Another string", 2000, false }, // Comment 2
{"Yet another string", 11111000,false}, // Comment 3
{NULL, 5, true }, // Comment 4
};
No estoy seguro de si esto cuenta como modismo, pero tiendo a usar comentarios en línea estilo doxygen incluso cuando el proyecto no está usando, sin embargo, doxygen ...
bool MyObjects::isUpToSomething() ///< Is my object up to something
(aparte, mis comentarios no son por lo general tan cojos).
No hay favoritos, pero corregiré el código que tiene:
- pestañas - causa desalineación en muchos IDEs y herramientas de revisión de código, porque no siempre coinciden en tabular en los espacios de mod 8.
- líneas de más de 80 columnas - seamos sinceros, las líneas más cortas son más legibles. Mi cerebro puede analizar la mayoría de las convenciones de codificación, siempre que las líneas sean cortas.
- líneas con espacios en blanco al final - git se quejará de ello como errores de espacio en blanco, que aparecen como manchas rojas en diffs, lo que es molesto.
Aquí hay una línea para encontrar los archivos ofensivos:
git grep -I -E ''<tab>|.{81,}| *$'' | cut -f1 -d: | sort -u
donde <tab>
es el carácter de tabulación (la expresión regular POSIX no funciona / t)
re: ididak
Repara el código que rompe las declaraciones largas en demasiadas líneas cortas.
Reconozcámoslo: ya no son los años 90. Si su empresa no puede pagar pantallas panorámicas para sus codificadores, necesita obtener un mejor trabajo :)
Después de trabajar con alguien que estaba parcialmente ciego, y a petición suya, cambié a usar muchos más espacios. No me gustó en ese momento, pero ahora lo prefiero. Fuera de mi cabeza, el único lugar donde no hay espacios en blanco entre los identificadores y las palabras clave y lo que sucede después de un nombre de función y antes de los siguientes paréntesis.
void foo( int a, int b )
{
int c = a + ( a * ( a * b ) );
if ( c > 12 )
c += 9;
return foo( 2, c );
}
Tiendo a poner otro en todos mis ifs.
if (condition)
{
complicated code goes here
}
else
{
/* This is a comment as to why the else path isn''t significant */
}
Aunque molesta a mis compañeros de trabajo. Se puede decir de un vistazo, que consideré el caso else durante la codificación.
Plantilla y gancho
Esta es una manera de manejar tanto como sea posible en un marco y dar una puerta o gancho para la personalización de los usuarios de un marco. También conocido como Hotspot y método de plantilla .
class Class {
void PrintInvoice(); // Called Template (boilerplate) which uses CalcRate()
virtual void CalcRate() = 0; // Called Hook
}
class SubClass : public Class {
virtual void CalcRate(); // Customized method
}
Descrito por Wolfgang Pree en su libro Design Patterns for Object-Oriented Software Development .
Polimorfismo de tiempo de compilación
(También conocido como polimorfismo sintáctico y polimorfismo estático, contraste con el polimorfismo en tiempo de ejecución).
Con las funciones de plantilla, uno puede escribir código que se basa en constructores de tipos y firmas de llamadas de familias de tipos parametrizados, sin tener que introducir una clase base común.
En el libro Elements of Programming , los autores se refieren a este tratamiento de los tipos como géneros abstractos . Con los conceptos, uno puede especificar los requisitos en tales parámetros de tipo, aunque C ++ no exige tales especificaciones.
Dos ejemplos simples:
#include <stdexcept>
template <typename T>
T twice(T n) {
return 2 * n;
}
InIt find(InIt f, InIt l,
typename std::iterator_traits<InIt>::reference v)
{
while (f != l && *f != v)
++f;
return f;
}
int main(int argc, char* argv[]) {
if (6 != twice(3))
throw std::logic_error("3 x 2 = 6");
int const nums[] = { 1, 2, 3 };
if (nums + 4 != find(nums, nums + 4, 42))
throw std::logic_error("42 should not have been found.");
return 0;
}
Se puede llamar twice
con cualquier tipo regular que tenga definido un operador binario *
. Del mismo modo, uno puede llamar a find()
con cualquier tipo que sea comparable y ese modelo Input Iterator . Un conjunto de códigos funciona de manera similar en diferentes tipos, sin clases básicas compartidas a la vista.
Por supuesto, lo que está sucediendo aquí es que es el mismo código fuente que se expande en varias funciones específicas de tipo en el momento de creación de instancias de la plantilla, cada una con un código máquina generado por separado. Acomodar el mismo conjunto de tipos sin plantillas habría requerido ya sea 1) funciones separadas escritas a mano con firmas específicas, o 2) polimorfismo en tiempo de ejecución a través de funciones virtuales.
RAII: Adquisición de recursos es inicialización
RAII puede ser el modismo más importante. Es la idea de que los recursos se deben mapear a los objetos, de modo que sus vidas se gestionen automáticamente de acuerdo con el alcance en el que se declaran esos objetos.
Por ejemplo, si se declara un identificador de archivo en la pila, se debe cerrar implícitamente una vez que regresemos de la función (o ciclo, o el alcance que se haya declarado dentro). Si se asignó una asignación de memoria dinámica como miembro de una clase, debería liberarse implícitamente cuando se destruye esa instancia de clase. Y así. Todo tipo de asignaciones de memoria de recursos, identificadores de archivos, conexiones de bases de datos, sockets y cualquier otro tipo de recurso que deba adquirirse y liberarse deben incluirse dentro de una clase RAII, cuya duración está determinada por el alcance en el que estuvo declarado.
Una ventaja importante de esto es que C ++ garantiza que se invocan destructores cuando un objeto sale del ámbito, independientemente de cómo el control abandone ese ámbito . Incluso si se lanza una excepción, todos los objetos locales saldrán del alcance y, por lo tanto, sus recursos asociados se limpiarán.
void foo() {
std::fstream file("bar.txt"); // open a file "bar.txt"
if (rand() % 2) {
// if this exception is thrown, we leave the function, and so
// file''s destructor is called, which closes the file handle.
throw std::exception();
}
// if the exception is not called, we leave the function normally, and so
// again, file''s destructor is called, which closes the file handle.
}
Independientemente de cómo dejamos la función, y de lo que sucede después de que se abre el archivo, no necesitamos cerrar explícitamente el archivo ni manejar excepciones (por ejemplo, try-finally) dentro de esa función. En cambio, el archivo se limpia porque está vinculado a un objeto local que se destruye cuando se sale del alcance.
RAII también se conoce menos comúnmente como SBRM (gestión de recursos de alcance).
Ver también:
- ScopeGuard permite que el código "invoque automáticamente una operación ''deshacer'' ... en el caso de que se produzca una excepción".
pImpl: Puntero a la implementación
La expresión pImpl es una forma muy útil de desacoplar la interfaz de una clase desde su implementación.
Normalmente, una definición de clase debe contener variables miembro así como métodos, que pueden exponer demasiada información. Por ejemplo, una variable miembro puede ser de un tipo definido en un encabezado que no deseamos incluir en todas partes.
El encabezado windows.h
es un buen ejemplo aquí. Es posible que deseemos incluir un HANDLE
u otro tipo de Win32 dentro de una clase, pero no podemos poner un HANDLE
en la definición de clase sin tener que incluir windows.h
todos los lugares donde se usa la clase.
La solución entonces es crear una IMPLivación IMPLivada o P ointer-to- IMPL ementation de la clase, y dejar que la implementación pública almacene solo un puntero al privado, y reenviar todos los métodos miembros.
Por ejemplo:
class private_foo; // a forward declaration a pointer may be used
// foo.h
class foo {
public:
foo();
~foo();
void bar();
private:
private_foo* pImpl;
};
// foo.cpp
#include whichever header defines the types T and U
// define the private implementation class
class private_foo {
public:
void bar() { /*...*/ }
private:
T member1;
U member2;
};
// fill in the public interface function definitions:
foo::foo() : pImpl(new private_foo()) {}
foo::~foo() { delete pImpl; }
void foo::bar() { pImpl->bar(); }
La implementación de foo
ahora está desacoplada de su interfaz pública, por lo que
- puede usar miembros y tipos de otros encabezados sin que estas dependencias estén presentes cuando se usa la clase, y
- la implementación se puede modificar sin forzar una recompilación del código que usa la clase.
Los usuarios de la clase simplemente incluyen el encabezado, que no contiene nada específico sobre la implementación de la clase. Todos los detalles de implementación están contenidos dentro de foo.cpp
.
Sugeriría PIMPL o como James Coplien lo llamó originalmente "manejar el cuerpo".
Esta expresión idiomática le permite desacoplar completamente la interfaz de la implementación. Al trabajar en la reescritura y reedición de un importante componente de middleware de CORBA, este modismo se utilizó para desacoplar completamente la API de la implementación.
Esto prácticamente eliminó cualquier posibilidad de ingeniería inversa.
Un excelente recurso para los modismos de C ++ es el excelente libro de James Coplien " Estilos y modismos avanzados de programación en C ++ ". ¡Muy recomendable!
Editar: Como se señala a continuación por Neil, este libro está bastante desactualizado ya que muchas de sus recomendaciones se están incorporando al estándar C ++. Sin embargo, todavía lo encuentro como una fuente de información útil, especialmente en la forma de su documento PLoP sobre modismos en C ++ donde muchos modismos fueron refundidos en forma de patterm.
if / while / for parenthesized expression (s) WITH a space separator (separador de espacio)
if (expression) // preferred - if keyword sticks out more
vs.
if(expression) // looks too much like a void function call
Supongo que esto implica que me gusta que mi función llame a NO tener un separador de espacio
foo(parm1, parm2);
Documente los valores de retorno en la línea de función, por lo que son muy fáciles de encontrar.
int function(void) /* return 1 on success, 0 on failure */
{
return 1;
};
Public Top - Privado Abajo
Una optimización aparentemente pequeña, pero desde que cambié a esta convención, tengo un momento mucho más divertido para captar mis clases, especialmente después de 42 años.
Tener una visibilidad consistente para los miembros, yendo desde puntos de interés frecuente hasta aburridos, es extremadamente útil, especialmente cuando el código debe ser autodocumentado.
(nota al margen para los usuarios de qt: las ranuras vienen antes de las señales porque deben poder llamarse como funciones de miembros que no son de ranura, y aparte de sus ranuras, no se distinguen de las que no son de ranura)
- Público, protegido, privado
- luego Factory, ctor, dtor, copy, swapping
- luego, la interfaz de la clase. Al final, en una sección
private:
separada, viene la información (idealmente solo un puntero implícito).
Esta regla también ayuda mucho si tiene problemas para mantener su declaración de clase despejada.
class Widget : public Purple {
public:
// Factory methods.
Widget FromRadians (float);
Widget FromDegrees (float);
// Ctors, rule of three, swap
Widget();
Widget (Widget const&);
Widget &operator = (Widget const &);
void swap (Widget &) throw();
// Member methods.
float area() const;
// in case of qt {{
public slots:
void invalidateBlackHole();
signals:
void areaChanged (float);
// }}
protected:
// same as public, but for protected members
private:
// same as public, but for private members
private:
// data
float widgetness_;
bool isMale_;
};
Al crear enumeraciones, colóquelas en un espacio de nombres para que pueda acceder a ellas con un nombre significativo:
namespace EntityType {
enum Enum {
Ground = 0,
Human,
Aerial,
Total
};
}
void foo(EntityType::Enum entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
EDITAR : Sin embargo, esta técnica se ha vuelto obsoleta en C ++ 11. La enumeración por ámbitos (declarada con enum class
o enum struct
) debería usarse en su lugar: es más segura, conciso y flexible. Con enumeraciones antiguas, los valores se colocan en el ámbito externo. Con la enumeración de nuevo estilo, se ubican dentro del alcance del nombre de la enum class
.
Ejemplo anterior reescrito utilizando enumeración con ámbito (también conocida como enumeración fuertemente tipada ):
enum class EntityType {
Ground = 0,
Human,
Aerial,
Total
};
void foo(EntityType entityType)
{
if (entityType == EntityType::Ground) {
/*code*/
}
}
Existen otros beneficios significativos al usar enumeraciones con ámbito: ausencia de conversión implícita, posible declaración directa y capacidad para usar un tipo subyacente personalizado (no el valor predeterminado int
).
Realmente me gusta poner una pequeña declaración en la misma línea que si
int myFunc(int x) {
if(x >20) return -1;
//do other stuff ....
}
En las declaraciones if
, cuando hay condiciones difíciles, puede mostrar claramente en qué nivel está usando cada condición.
if ( ( (var1A == var2A)
|| (var1B == var2B))
&& ( (var1C == var2C)
|| (var1D == var2D)))
{
// do something
}
CRTP: patrón de plantilla curiosamente recurrente
CRTP ocurre cuando pasa una clase como parámetro de plantilla a su clase base:
template<class Derived>
struct BaseCRTP {};
struct Example : BaseCRTP<Example> {};
Dentro de la clase base, puede obtener la instancia derivada, completa con el tipo derivado , simplemente mediante conversión (ya sea static_cast o dynamic_cast work):
template<class Derived>
struct BaseCRTP {
void call_foo() {
Derived& self = *static_cast<Derived*>(this);
self.foo();
}
};
struct Example : BaseCRTP<Example> {
void foo() { cout << "foo()/n"; }
};
En efecto, call_foo ha sido inyectado en la clase derivada con pleno acceso a los miembros de la clase derivada.
Siéntase libre de editar y agregar ejemplos específicos de uso, posiblemente a otras publicaciones de SO .
Copiar-intercambiar
La expresión de intercambio de copia proporciona copias seguras de excepciones. Requiere que se implemente un copiador y copiador correctos.
struct String {
String(String const& other);
String& operator=(String copy) { // passed by value
copy.swap(*this); // nothrow swap
return *this; // old resources now in copy, released in its dtor
}
void swap(String& other) throw() {
using std::swap; // enable ADL, defaulting to std::swap
swap(data_members, other.data_members);
}
private:
Various data_members;
};
void swap(String& a, String& b) { // provide non-member for ADL
a.swap(b);
}
También puede implementar el método de intercambio con ADL (Argument Dependent Lookup) directamente .
Este idioma es importante porque maneja la autoasignación [1] , hace la fuerte garantía de excepción [2] y, a menudo, es muy fácil de escribir.
[1] Aunque la autoasignación no se maneja de la manera más eficiente posible, se supone que es rara , por lo que si nunca ocurre, en realidad es más rápida.
[2] Si se lanza una excepción, el estado del objeto ( *this
) no se modifica.
No sé si califica como un modismo, exactamente, pero un poco de programación de plantillas de alta resistencia depende (a menudo) de SFINAE (la falla de sustitución no es un error). Algunas de las respuestas a una pregunta anterior tienen ejemplos.