Pregunta

Recuerdo que aprendí por primera vez sobre vectores en STL y, después de un tiempo, quise usar un vector de bools para uno de mis proyectos.Después de ver un comportamiento extraño e investigar un poco, descubrí que un vector de bools no es realmente un vector de bools.

¿Existen otros errores comunes que se deban evitar en C++?

¿Fue útil?

Solución

Una lista breve podría ser:

  • Evite pérdidas de memoria mediante el uso de punteros compartidos para gestionar la asignación y limpieza de la memoria.
  • Utilizar el La adquisición de recursos es inicialización (RAII) modismo para gestionar la limpieza de recursos, especialmente en presencia de excepciones
  • Evite llamar a funciones virtuales en constructores
  • Emplee técnicas de codificación minimalistas siempre que sea posible; por ejemplo, declarar variables solo cuando sea necesario, determinar el alcance de las variables y diseñar desde el principio cuando sea posible.
  • Comprenda realmente el manejo de excepciones en su código, tanto con respecto a las excepciones que usted lanza como a las lanzadas por clases que puede estar usando indirectamente.Esto es especialmente importante en presencia de plantillas.

Por supuesto, RAII, los punteros compartidos y la codificación minimalista no son específicos de C++, pero ayudan a evitar problemas que surgen con frecuencia al desarrollar en el lenguaje.

Algunos libros excelentes sobre este tema son:

  • C++ efectivo - Scott Meyers
  • C++ más eficaz - Scott Meyers
  • Estándares de codificación C++ - Sutter & Alexandrescu
  • Preguntas frecuentes sobre C++ - Cline

Leer estos libros me ha ayudado más que nada a evitar el tipo de trampas por las que preguntas.

Otros consejos

Errores en orden decreciente de importancia

En primer lugar, debes visitar el galardonado Preguntas frecuentes sobre C++.Tiene muchas buenas respuestas a los obstáculos.Si tienes más preguntas, visita ##c++ en irc.freenode.org en IRC.Estaremos encantados de ayudarle, si podemos.Tenga en cuenta que todos los errores siguientes están escritos originalmente.No se copian simplemente de fuentes aleatorias.


delete[] en new, delete en new[]

Solución:Hacer lo anterior produce un comportamiento indefinido:Todo puede pasar.Comprenda su código y lo que hace, y siempre delete[] lo que tu new[], y delete lo que tu new, entonces eso no sucederá.

Excepción:

typedef T type[N]; T * pT = new type; delete[] pT;

Necesitas delete[] aunque tu new, ya que agregaste una matriz.Así que si estás trabajando con typedef, tenga especial cuidado.


Llamar a una función virtual en un constructor o destructor

Solución:Llamar a una función virtual no llamará a las funciones primordiales en las clases derivadas.llamando a un función virtual pura en un constructor o destructor es un comportamiento indefinido.


Vocación delete o delete[] en un puntero ya eliminado

Solución:Asigne 0 a cada puntero que elimine.Vocación delete o delete[] en un puntero nulo no hace nada.


Tomando el tamaño de un puntero, cuando se va a calcular el número de elementos de una 'matriz'.

Solución:Pase la cantidad de elementos junto al puntero cuando necesite pasar una matriz como puntero a una función.Utilice la función propuesta aquí si toma el tamaño de una matriz, se supone que es realmente una matriz.


Usando un array como si fuera un puntero.Así, utilizando T ** para una matriz bidimensional.

Solución:Ver aquí por qué son diferentes y cómo los manejas.


Escribir en una cadena literal: char * c = "hello"; *c = 'B';

Solución:Asigne una matriz que se inicializa a partir de los datos del literal de cadena, luego puede escribir en ella:

char c[] = "hello"; *c = 'B';

Escribir en un literal de cadena es un comportamiento indefinido.De todos modos, la conversión anterior de un literal de cadena a char * es obsoleto.Por lo tanto, los compiladores probablemente le advertirán si aumenta el nivel de advertencia.


Crear recursos y luego olvidarse de liberarlos cuando algo falla.

Solución:Utilice punteros inteligentes como std::unique_ptr o std::shared_ptr como lo señalan otras respuestas.


Modificando un objeto dos veces como en este ejemplo: i = ++i;

Solución:Se suponía que lo anterior debía asignarse a i El valor de i+1.Pero lo que hace no está definido.En lugar de incrementar i y asignando el resultado, cambia i en el lado derecho también.Cambiar un objeto entre dos puntos de secuencia es un comportamiento indefinido.Los puntos de secuencia incluyen ||, &&, comma-operator, semicolon y entering a function (¡lista no exhaustiva!).Cambie el código al siguiente para que se comporte correctamente: i = i + 1;


Problemas varios

Olvidar vaciar las secuencias antes de llamar a una función de bloqueo como sleep.

Solución:Vacíe el flujo transmitiendo cualquiera de los dos std::endl en lugar de \n o llamando stream.flush();.


Declarar una función en lugar de una variable.

Solución:El problema surge porque el compilador interpreta, por ejemplo

Type t(other_type(value));

como una declaración de función de una función t regresando Type y tener un parámetro de tipo other_type Lo que es llamado value.Lo resuelves poniendo paréntesis alrededor del primer argumento.Ahora obtienes una variable t de tipo Type:

Type t((other_type(value)));

Llamar a la función de un objeto libre que solo está declarado en la unidad de traducción actual (.cpp archivo).

Solución:El estándar no define el orden de creación de objetos libres (en el ámbito del espacio de nombres) definidos en diferentes unidades de traducción.Llamar a una función miembro en un objeto aún no construido es un comportamiento indefinido.En su lugar, puede definir la siguiente función en la unidad de traducción del objeto y llamarla desde otras:

House & getTheHouse() { static House h; return h; }

Eso crearía el objeto a pedido y lo dejaría con un objeto completamente construido en el momento en que invoca funciones en él.


Definición de una plantilla en un .cpp archivo, mientras se usa en un diferente .cpp archivo.

Solución:Casi siempre obtendrás errores como undefined reference to ....Coloque todas las definiciones de plantilla en un encabezado, de modo que cuando el compilador las esté usando, ya pueda producir el código necesario.


static_cast<Derived*>(base); si base es un puntero a una clase base virtual de Derived.

Solución:Una clase base virtual es una base que ocurre sólo una vez, incluso si es heredada más de una vez por diferentes clases indirectamente en un árbol de herencia.Hacer lo anterior no está permitido por la Norma.Utilice Dynamic_cast para hacer eso y asegúrese de que su clase base sea polimórfica.


dynamic_cast<Derived*>(ptr_to_base); si la base no es polimórfica

Solución:El estándar no permite un downcast de un puntero o referencia cuando el objeto pasado no es polimórfico.Él o una de sus clases base debe tener una función virtual.


Hacer que tu función acepte T const **

Solución:Podrías pensar que es más seguro que usar T **, pero en realidad causará dolor de cabeza a las personas que quieran pasar T**:La norma no lo permite.Da un claro ejemplo de por qué no está permitido:

int main() {
    char const c = ’c’;
    char* pc;
    char const** pcc = &pc; //1: not allowed
    *pcc = &c;
    *pc = ’C’; //2: modifies a const object
}

aceptar siempre T const* const*; en cambio.

Otro hilo de trampas (cerrado) sobre C++, por lo que las personas que las buscan las encontrarán, es la pregunta de Stack Overflow. Errores de C++.

Algunos deben tener libros de C++ que le ayudarán a evitar los errores comunes de C++:

C++ efectivo
C++ más eficaz
STL efectivo

El libro Effective STL explica el problema del vector de bools :)

Brian tiene una gran lista:Yo agregaría "Marcar siempre los constructores de un solo argumento como explícitos (excepto en esos raros casos en los que desea la conversión automática)".

No es realmente un consejo específico, sino una pauta general:revisa tus fuentes.C++ es un lenguaje antiguo y ha cambiado mucho a lo largo de los años.Las mejores prácticas han cambiado con esto, pero desafortunadamente todavía hay mucha información antigua disponible.Ha habido algunas recomendaciones de libros muy buenas aquí; puedo comprar en segundo lugar cada uno de los libros de Scott Meyers C++.Familiarícese con Boost y con los estilos de codificación utilizados en Boost: las personas involucradas en ese proyecto están a la vanguardia del diseño de C++.

No reinventes la rueda.Familiarízate con STL y Boost, y utiliza sus instalaciones siempre que sea posible para rodar las tuyas.En particular, utilice cadenas y colecciones STL a menos que tenga una muy, muy buena razón para no hacerlo.Conozca muy bien auto_ptr y la biblioteca de punteros inteligentes de Boost, comprenda en qué circunstancias se debe utilizar cada tipo de puntero inteligente y luego use punteros inteligentes en todos los lugares donde, de otro modo, habría usado punteros sin formato.Su código será igual de eficiente y mucho menos propenso a sufrir pérdidas de memoria.

Utilice static_cast,dynamic_cast, const_cast y reinterpret_cast en lugar de conversiones estilo C.A diferencia de los yesos estilo C, le permitirán saber si realmente está solicitando un tipo de yeso diferente al que cree que está solicitando.Y se destacan visualmente, alertando al lector de que se está produciendo un reparto.

la pagina web Errores de C++ de Scott Wheeler cubre algunos de los principales problemas de C++.

Dos trampas que desearía no haber aprendido de la manera más difícil:

(1) Gran parte de la salida (como printf) se almacena en el búfer de forma predeterminada.Si está depurando código que falla y está utilizando declaraciones de depuración almacenadas en búfer, el último resultado que vea puede no realmente será la última declaración impresa encontrada en el código.La solución es vaciar el búfer después de cada impresión de depuración (o desactivar el búfer por completo).

(2) Tenga cuidado con las inicializaciones: (a) evite instancias de clases como globales/estáticas;y (b) intente inicializar todas sus variables miembro con algún valor seguro en un ctor, incluso si es un valor trivial como NULL para punteros.

Razonamiento:el orden de la inicialización de objetos globales no está garantizado (los globales incluyen variables estáticas), por lo que puede terminar con un código que parece fallar de manera no determinista, ya que depende de que el objeto X se inicialice antes que el objeto Y.Si no inicializa explícitamente una variable de tipo primitivo, como un miembro bool o una enumeración de una clase, terminará con valores diferentes en situaciones sorprendentes; nuevamente, el comportamiento puede parecer muy no determinista.

Ya lo he mencionado varias veces, pero los libros de Scott Meyers C++ efectivo y STL efectivo realmente valen su peso en oro por ayudar con C++.

Ahora que lo pienso, Steven Dewhurst Errores de C++ También es un excelente recurso "desde las trincheras".Su artículo sobre cómo generar sus propias excepciones y cómo deberían construirse realmente me ayudó en un proyecto.

Usando C++ como C.Tener un ciclo de creación y liberación en el código.

En C++, esto no es una excepción segura y, por lo tanto, es posible que no se ejecute la versión.En C++ utilizamos RAII para resolver este problema.

Todos los recursos que tienen una creación y liberación manual deben estar incluidos en un objeto para que estas acciones se realicen en el constructor/destructor.

// C Code
void myFunc()
{
    Plop*   plop = createMyPlopResource();

    // Use the plop

    releaseMyPlopResource(plop);
}

En C++, esto debería estar incluido en un objeto:

// C++
class PlopResource
{
    public:
        PlopResource()
        {
            mPlop=createMyPlopResource();
            // handle exceptions and errors.
        }
        ~PlopResource()
        {
             releaseMyPlopResource(mPlop);
        }
    private:
        Plop*  mPlop;
 };

void myFunc()
{
    PlopResource  plop;

    // Use the plop
    // Exception safe release on exit.
}

El libro Errores de C++ puede resultar útil.

Aquí hay algunos pozos en los que tuve la desgracia de caer.Todo esto tiene buenas razones que sólo entendí después de haber sido mordido por un comportamiento que me sorprendió.

  • virtual funciones en constructores no lo son.

  • No viole el ODR (regla de una definición), para eso están los espacios de nombres anónimos (entre otras cosas).

  • El orden de inicialización de los miembros depende del orden en que se declaran.

    class bar {
        vector<int> vec_;
        unsigned size_; // Note size_ declared *after* vec_
    public:
        bar(unsigned size)
            : size_(size)
            , vec_(size_) // size_ is uninitialized
            {}
    };
    
  • Valores predeterminados y virtual tienen una semántica diferente.

    class base {
    public:
        virtual foo(int i = 42) { cout << "base " << i; }
    };
    
    class derived : public base {
    public:
        virtual foo(int i = 12) { cout << "derived "<< i; }
    };
    
    derived d;
    base& b = d;
    b.foo(); // Outputs `derived 42`
    

El problema más importante para los desarrolladores principiantes es evitar la confusión entre C y C++.C++ nunca debe ser tratado como un simple C o C mejor con clases porque esto reduce su poder y puede hacerlo incluso peligroso (especialmente cuando se usa memoria como en C).

Verificar impulso.org.Proporciona muchas funciones adicionales, especialmente sus implementaciones de puntero inteligente.

PRQA tiene un excelente y gratuito estándar de codificación C++ Basado en libros de Scott Meyers, Bjarne Stroustrop y Herb Sutter.Reúne toda esta información en un solo documento.

  1. No leer el Preguntas frecuentes sobre C++ Lite.Explica muchas malas (y buenas) prácticas.
  2. No usando Aumentar.Te ahorrarás mucha frustración si aprovechas Boost siempre que sea posible.

Tenga cuidado al utilizar punteros inteligentes y clases de contenedores.

Evitar pseudoclases y cuasiclases...Sobrediseño básicamente.

Olvidando definir un destructor de clase base virtual.Esto significa que llamar delete en una Base* no terminará destruyendo la parte derivada.

Mantenga los espacios de nombres rectos (incluidos estructura, clase, espacio de nombres y uso).Esa es mi frustración número uno cuando el programa simplemente no se compila.

Para equivocarse, utilice mucho los punteros rectos.En su lugar, utilice RAII para casi cualquier cosa, asegurándose, por supuesto, de utilizar los punteros inteligentes correctos.Si escribe "eliminar" en cualquier lugar fuera de un identificador o clase de tipo puntero, es muy probable que lo esté haciendo mal.

  • Blizpasta.Ese es enorme, lo veo mucho...

  • Las variables no inicializadas son un gran error que cometen mis alumnos.Mucha gente de Java olvida que simplemente decir "int counter" no pone el contador en 0.Dado que tiene que definir variables en el archivo h (e inicializarlas en el constructor/configuración de un objeto), es fácil de olvidar.

  • Errores uno por uno activados for acceso a bucles/matriz.

  • No limpiar adecuadamente el código objeto cuando se inicia el vudú.

  • static_cast abatido en una clase base virtual

No precisamente...Ahora sobre mi idea errónea:pensé que A a continuación había una clase base virtual cuando en realidad no lo es;es, según 10.3.1, un clase polimórfica.Usando static_cast aquí parece estar bien.

struct B { virtual ~B() {} };

struct D : B { };

En resumen, sí, se trata de un peligro peligroso.

Siempre verifique un puntero antes de eliminar la referencia a él.En C, normalmente se puede contar con un bloqueo en el punto en el que se elimina la referencia a un puntero incorrecto;en C++, puede crear una referencia no válida que fallará en un lugar muy alejado del origen del problema.

class SomeClass
{
    ...
    void DoSomething()
    {
        ++counter;    // crash here!
    }
    int counter;
};

void Foo(SomeClass & ref)
{
    ...
    ref.DoSomething();    // if DoSomething is virtual, you might crash here
    ...
}

void Bar(SomeClass * ptr)
{
    Foo(*ptr);    // if ptr is NULL, you have created an invalid reference
                  // which probably WILL NOT crash here
}

olvidando un & y creando así una copia en lugar de una referencia.

Esto me pasó dos veces de diferentes maneras:

  • Una instancia estaba en una lista de argumentos, lo que provocó que se colocara un objeto grande en la pila con el resultado de un desbordamiento de la pila y una falla del sistema integrado.

  • me olvidé del & en una variable de instancia, con el efecto de que el objeto fue copiado.Después de registrarme como oyente de la copia, me pregunté por qué nunca recibí las devoluciones de llamada del objeto original.

Ambos eran bastante difíciles de detectar, porque la diferencia es pequeña y difícil de ver y, por lo demás, los objetos y las referencias se usan sintácticamente de la misma manera.

La intención es (x == 10):

if (x = 10) {
    //Do something
}

Pensé que nunca cometería este error, pero en realidad lo cometí recientemente.

El ensayo/artículo Consejos, referencias y valores. es muy útil.Se trata de evitar evitar escollos y buenas prácticas.También puede navegar por todo el sitio, que contiene consejos de programación, principalmente para C++.

Pasé muchos años desarrollando C++.escribí un sumario rápido de problemas que tuve con él hace años.Los compiladores que cumplen con los estándares ya no son un problema, pero sospecho que los otros errores descritos siguen siendo válidos.

#include <boost/shared_ptr.hpp>
class A {
public:
  void nuke() {
     boost::shared_ptr<A> (this);
  }
};

int main(int argc, char** argv) {
  A a;
  a.nuke();
  return(0);
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top