Después de leer este famoso discurso de Linus Torvalds , me pregunté cuáles son en realidad todas las trampas para los programadores en C ++. No me estoy refiriendo explícitamente a los errores tipográficos o al mal flujo del programa como se trata en esta pregunta y sus respuestas , sino a más errores de alto nivel que no son detectados por el compilador y que no producen errores obvios en la primera ejecución, errores de diseño completos, cosas que son improbables en C pero que probablemente sean hechas en C ++ por los recién llegados que no entienden las implicaciones completas de su código.
También acojo con beneplácito las respuestas que señalan una gran disminución del rendimiento donde generalmente no se esperaría. Un ejemplo de lo que uno de mis profesores me contó una vez sobre un generador de analizador LR (1) que escribí:
Ha utilizado demasiadas instancias de herencia y virtualidad innecesarias. La herencia hace que un diseño sea mucho más complicado (e ineficiente debido al subsistema RTTI (inferencia de tipo de tiempo de ejecución)) y, por lo tanto, solo debe usarse donde tenga sentido, por ejemplo, para las acciones en la tabla de análisis. Debido a que hace un uso intensivo de las plantillas, prácticamente no necesita herencia ".
virtual
funciones, ¿verdad?dynamic_cast
debe tener éxito o no, y algunas otras cosas, pero la reflexión cubre mucho más, incluida la posibilidad de recuperar información sobre los atributos o funciones de los miembros, eso no es presente en C ++.Respuestas:
Torvalds está hablando por el culo aquí.
De acuerdo, por qué está hablando por el culo:
En primer lugar, su perorata no es nada PERO despotricar. Hay muy poco contenido real aquí. La única razón por la que es realmente famoso o incluso ligeramente respetado es porque fue creado por el Dios de Linux. Su argumento principal es que C ++ es una mierda y le gusta molestar a la gente de C ++. Por supuesto, no hay ninguna razón para responder a eso y cualquiera que lo considere un argumento razonable está más allá de la conversación de todos modos.
En cuanto a lo que podría brillar como sus puntos más objetivos:
Básicamente, Torvalds está hablando por el culo. No hay argumentos inteligibles sobre nada. Esperar una refutación seria de tales tonterías es simplemente una tontería. Me dicen que "expanda" una refutación de algo sobre lo que se espera que amplíe si es donde yo lo dije. Si realmente, honestamente, mira lo que dijo Torvalds, verías que en realidad no dijo nada.
Solo porque Dios dice que no significa que tenga sentido o que deba tomarse más en serio que si un bozo al azar lo dijera. A decir verdad, Dios es solo otro bozo al azar.
Respondiendo a la pregunta real:
Probablemente, la peor y más común práctica incorrecta de C ++ es tratarla como C. El uso continuo de las funciones de C API como printf, gets (también considerado malo en C), strtok, etc. no solo no logran aprovechar la potencia proporcionada. por el sistema de tipo más estricto, inevitablemente conducen a más complicaciones al tratar de interactuar con el código C ++ "real". Básicamente, haga exactamente lo contrario de lo que Torvalds está recomendando.
Aprenda a aprovechar el STL y Boost para obtener una mayor detección de errores en el tiempo de compilación y hacer su vida más fácil de otras maneras generales (el tokenizer boost, por ejemplo, es de tipo seguro Y una mejor interfaz). Es cierto que tendrá que aprender a leer los errores de la plantilla, lo cual es desalentador al principio, pero (en mi experiencia de todos modos) es francamente mucho más fácil que tratar de depurar algo que genera un comportamiento indefinido durante el tiempo de ejecución, que hace la API C Muy fácil de hacer.
No quiere decir que C no sea tan bueno. Por supuesto, me gusta C ++ mejor. A los programadores de C les gusta C mejor. Hay compensaciones y gustos subjetivos en el juego. También hay mucha información errónea y FUD flotando. Diría que hay más FUD y desinformación flotando sobre C ++, pero soy parcial en este sentido. Por ejemplo, los problemas de "hinchazón" y "rendimiento" que supuestamente tiene C ++ no son en realidad problemas importantes la mayoría de las veces y, ciertamente, están fuera de las proporciones de la realidad.
En cuanto a los problemas a los que se refiere su profesor, estos no son exclusivos de C ++. En OOP (y en programación genérica) desea preferir la composición sobre la herencia. La herencia es la relación de acoplamiento más fuerte posible que existe en todos los lenguajes OO. C ++ agrega uno más que es más fuerte, la amistad. La herencia polimórfica debe usarse para representar abstracciones y relaciones "es-a", nunca debe usarse para su reutilización. Este es el segundo error más grande que puede cometer en C ++, y es bastante grande, pero está lejos de ser exclusivo del lenguaje. También puede crear relaciones de herencia demasiado complejas en C # o Java, y tendrán exactamente los mismos problemas.
fuente
Siempre he pensado que los peligros de C ++ fueron muy exagerados por los programadores inexpertos de C with Classes.
Sí, C ++ es más difícil de aprender que algo como Java, pero si programa utilizando técnicas modernas, es bastante fácil escribir programas robustos. Sinceramente, no tengo que mucho más difícil de una programación horaria en C ++ que yo en lenguajes como Java, y a menudo me encuentro que faltan ciertas abstracciones C ++ como plantillas y RAII cuando diseño en otros idiomas.
Dicho esto, incluso después de años de programación en C ++, de vez en cuando cometeré un error realmente estúpido que no sería posible en un lenguaje de nivel superior. Un error común en C ++ es ignorar la vida útil de los objetos: en Java y C # generalmente no tiene que preocuparse por la vida útil de los objetos *, porque todos los objetos existen en el montón y son administrados por un recolector de basura mágico.
Ahora, en C ++ moderno, por lo general , tampoco necesita preocuparse demasiado por la vida útil de los objetos. Tiene destructores e indicadores inteligentes que administran la vida útil de los objetos por usted. El 99% de las veces, esto funciona de maravilla. Pero de vez en cuando, un puntero colgante (o referencia) lo atornillará. Por ejemplo, recientemente tuve un objeto (llamémoslo
Foo
) que contenía una variable de referencia interna a otro objeto (llamémosloBar
). En un momento, estúpidamente arreglé las cosas para queBar
salieran antes del alcanceFoo
, peroFoo
el destructor terminó llamando a una función miembro deBar
. No hace falta decir que las cosas no salieron bien.Ahora, realmente no puedo culpar a C ++ por esto. Era mi propio mal diseño, pero el punto es que este tipo de cosas no sucedería en un lenguaje administrado de nivel superior. Incluso con punteros inteligentes y similares, a veces aún debe tener una conciencia del tiempo de vida del objeto.
* Si el recurso que se administra es memoria, es decir.
fuente
La diferencia en el código suele estar más relacionada con el programador que con el lenguaje. En particular, un buen programador de C ++ y un programador de C llegarán a soluciones igualmente buenas (aunque diferentes). Ahora, C es un lenguaje más simple (como lenguaje) y eso significa que hay menos abstracciones y más visibilidad sobre lo que el código realmente hace.
Una parte de su diatriba (es conocido por sus diatribas contra C ++) se basa en el hecho de que más personas tomarán C ++ y escribirán código sin comprender realmente lo que algunas de las abstracciones ocultan y hacen suposiciones erróneas.
fuente
std::vector<bool>
cambio de cada valor?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? ¿En qué se abstrae*it = !*it;
?std::vector<bool>
es un error bien conocido, pero es un muy buen ejemplo de lo que se está discutiendo: las abstracciones son buenas, pero hay que tener cuidado con lo que ocultan. Lo mismo puede suceder y sucederá en el código de usuario. Para empezar, he visto que tanto las personas en C ++ como en Java usan excepciones para realizar el control de flujo, y un código que parece una llamada de función de anidación que en realidad es un iniciador de excepciones de rescate:void endOperation();
implementado comothrow EndOperation;
. Un buen programador evitará esas construcciones sorprendentes , pero el hecho es que puedes encontrarlas.Uso excesivo de
try/catch
bloques.Esto generalmente proviene de lenguajes como Java y la gente argumentará que C ++ carece de una
finalize
cláusula.Pero este código exhibe dos problemas:
file
antes deltry/catch
, porque en realidadclose
no puede existir un archivo que no existacatch
. Esto lleva a una "fuga de alcance" quefile
es visible después de haber sido cerrada. Puede agregar un bloque pero ...: /return
en medio deltry
alcance, entonces el archivo no está cerrado (razón por la cual las personas se quejan de la falta definalize
cláusula)Sin embargo, en C ++, tenemos formas mucho más eficientes de tratar este problema que:
finalize
using
defer
Tenemos RAII, cuya propiedad realmente interesante se resume mejor como
SBRM
(Scoped Bound Resources Management).Al crear la clase de modo que su destructor limpie los recursos que posee, ¡no tenemos la responsabilidad de administrar el recurso en todos y cada uno de sus usuarios!
Esta es la característica que extraño en cualquier otro idioma, y probablemente la que más se olvida.
La verdad es que rara vez es necesario incluso escribir un
try/catch
bloque en C ++, aparte en el nivel superior para evitar la terminación sin iniciar sesión.fuente
fopen
yfclose
aquí.) RAII es la manera "correcta" de hacer las cosas aquí, pero es un inconveniente para las personas que quieren utilizar bibliotecas de C de C ++ .File file("some.txt");
y eso es todo (noopen
, noclose
, notry
...)Un error común que se ajusta a sus criterios es no entender cómo funcionan los constructores de copias cuando se trata de memoria asignada en su clase. Perdí la cuenta de la cantidad de tiempo que pasé arreglando fallas o pérdidas de memoria porque un 'novato' colocó sus objetos en un mapa o vector y no escribió los constructores y destructores de copias correctamente.
Desafortunadamente, C ++ está lleno de trampas 'ocultas' como esta. Pero quejarse de eso es como quejarse de que fue a Francia y no podía entender lo que la gente decía. Si vas a ir allí, aprende el idioma.
fuente
C ++ permite una gran variedad de características y estilos de programación, pero eso no significa que estas sean realmente buenas formas de utilizar C ++. Y de hecho, es increíblemente fácil usar C ++ incorrectamente.
Tiene que ser aprendido y entendido correctamente , solo aprender haciendo (o usarlo como si se usara otro idioma) conducirá a un código ineficiente y propenso a errores.
fuente
Bueno ... Para empezar, puedes leer las preguntas frecuentes de C ++ Lite
Luego, varias personas han construido carreras escribiendo libros sobre las complejidades de C ++:
Herb Sutter y Scott Meyers a saber.
En cuanto a la falta de sustancia de Torvalds ... venga gente, en serio: ningún otro idioma ha derramado tanta tinta sobre el tratamiento de los matices del idioma. Sus libros de Python, Ruby y Java se centran en aplicaciones de escritura ... sus libros de C ++ se centran en características / consejos / trampas de lenguaje tonto.
fuente
Las plantillas demasiado pesadas pueden no provocar errores al principio. Sin embargo, a medida que pase el tiempo, las personas necesitarán modificar ese código, y tendrán dificultades para comprender una plantilla enorme. Ahí es cuando entran los errores: los malentendidos provocan comentarios de "Compila y ejecuta", que a menudo conducen a un código casi correcto.
En general, si me veo haciendo una plantilla genérica profunda de tres niveles, me detengo y pienso cómo podría reducirse a una. A menudo, el problema se resuelve extrayendo funciones o clases.
fuente
Advertencia: esto no es tanto una respuesta como una crítica de la charla que "usuario desconocido" vincula en su respuesta.
Su primer punto principal es el (supuestamente) "estándar siempre cambiante". En realidad, todos los ejemplos que da se relacionan con cambios en C ++ antes de que hubiera un estándar. Desde 1998 (cuando se finalizó el primer estándar de C ++) los cambios en el lenguaje han sido bastante mínimos; de hecho, muchos argumentan que el verdadero problema es que deberían haberse realizado más cambios. Estoy razonablemente seguro de que todo el código que se ajustaba al estándar original de C ++ todavía se ajusta al estándar actual. Aunque es algo menos seguro, a menos que algo cambie rápidamente (y de manera bastante inesperada), lo mismo será bastante cierto con el próximo estándar C ++ también (en teoría, todo el código que usó
export
se romperá, pero prácticamente no existe ninguno; desde un punto de vista práctico no es un problema). Puedo pensar en algunos otros idiomas, sistemas operativos (o mucho más relacionados con la computadora) que puedan hacer tal afirmación.Luego entra en "estilos siempre cambiantes". Una vez más, la mayoría de sus puntos están bastante cerca de tonterías. Intenta caracterizarlo
for (int i=0; i<n;i++)
como "viejo y reventado" yfor (int i(0); i!=n;++i)
"nuevo calor". La realidad es que si bien existen tipos para los que dichos cambios podrían tener sentido,int
ya que no hay diferencia, e incluso cuando se puede obtener algo, rara vez es necesario escribir un código correcto o correcto. Incluso en el mejor de los casos, está haciendo una montaña de un topo.Su próximo reclamo es que C ++ está "optimizando en la dirección equivocada", específicamente, aunque admite que usar buenas bibliotecas es fácil, afirma que C ++ "hace que escribir buenas bibliotecas sea casi imposible". Aquí, creo que es uno de sus errores más fundamentales. En realidad, escribir buenas bibliotecas para casi cualquier idioma es extremadamente difícil. Como mínimo, escribir una buena biblioteca requiere comprender algunos dominios problemáticos tan bien que su código funciona para una multitud de posibles aplicaciones en (o relacionadas con) ese dominio. La mayor parte de lo que C ++ realmente hace es "elevar el listón": después de ver cuán mejor puede ser una biblioteca , las personas rara vez están dispuestas a volver a escribir el tipo de basura que tendrían de lo contrario.los codificadores realmente buenos escriben bastantes bibliotecas, que luego pueden ser utilizadas (fácilmente, como él admite) por "el resto de nosotros". Este es realmente un caso donde "eso no es un error, es una característica".
No trataré de llegar a cada punto en orden (eso tomaría páginas), pero saltaré directamente a su punto de cierre. Cita a Bjarne diciendo: "la optimización de todo el programa se puede utilizar para eliminar tablas de funciones virtuales y datos RTTI no utilizados. Tal análisis es particularmente adecuado para programas relativamente pequeños que no usan enlaces dinámicos".
Él critica esto haciendo una afirmación sin respaldo de que "Este es un problema realmente difícil", incluso yendo tan lejos como comparándolo con el problema de detención. En realidad, no es nada de eso, de hecho, el enlazador incluido con Zortech C ++ (más o menos el primero compilador de C ++ para MS-DOS, en la década de 1980) hizo esto. Es cierto que es difícil estar seguro de que se hayan eliminado todos los datos posiblemente extraños, pero sigue siendo razonable hacer un trabajo bastante justo.
Sin embargo, independientemente de eso, el punto mucho más importante es que esto es completamente irrelevante para la mayoría de los programadores en cualquier caso. Como aquellos de nosotros que hemos desarmado bastante código sabemos, a menos que escriba lenguaje ensamblador sin ninguna biblioteca, sus ejecutables casi seguramente contienen una buena cantidad de "cosas" (código y datos, en casos típicos) que usted probablemente ni siquiera sé sobre eso, sin mencionar el uso real. Para la mayoría de las personas, la mayoría de las veces, simplemente no importa: a menos que esté desarrollando para los sistemas integrados más pequeños, ese consumo de almacenamiento adicional es simplemente irrelevante.
Al final, es cierto que esta diatriba tiene un poco más de sustancia que la idiotez de Linus, pero eso le da exactamente la condena con elogios que merece.
fuente
Como programador en C que tuvo que codificar en C ++ debido a circunstancias inevitables, aquí está mi experiencia. Hay muy pocas cosas que utilizo que son C ++ y la mayoría se adhieren a C. La razón principal es porque no entiendo C ++ tan bien. Tenía / no tenía un mentor que me mostrara las complejidades de C ++ y cómo escribir un buen código en él. Y sin la guía de un muy buen código de C ++, es extremadamente difícil escribir un buen código en C ++. En mi humilde opinión, este es el mayor inconveniente de C ++ porque los buenos codificadores de C ++ dispuestos a manejar principiantes son difíciles de encontrar.
Algunos de los éxitos de rendimiento que he visto generalmente se deben a la asignación de memoria mágica de STL (sí, puede cambiar el asignador, pero ¿quién lo hace cuando comienza con C ++?). Usualmente escuchas argumentos de expertos en C ++ de que los vectores y las matrices ofrecen un rendimiento similar, porque los vectores usan matrices internamente y la abstracción es súper eficiente. He encontrado que esto es cierto en la práctica para el acceso de vectores y la modificación de los valores existentes. Pero no es cierto para agregar una nueva entrada, construcción y destrucción de vectores. gprof mostró que el 25% del tiempo acumulado para una aplicación se gastó en constructores de vectores, destructores, memmove (para la reubicación de todo el vector para agregar un nuevo elemento) y otros operadores de vectores sobrecargados (como ++).
En la misma aplicación, se usó el vector de somethingSmall para representar un somethingBig. No había necesidad de acceso aleatorio de algo pequeño en algo grande. Todavía se usó un vector en lugar de una lista. ¿La razón por la que se usó el vector? Porque el codificador original estaba familiarizado con la matriz como la sintaxis de vectores y no estaba muy familiarizado con los iteradores necesarios para las listas (sí, él es de un fondo C). Continúa para demostrar que se necesita mucha orientación de expertos para obtener C ++ correctamente. C ofrece tan pocas construcciones básicas sin absolutamente ninguna abstracción, que puede hacerlo correctamente mucho más fácilmente que C ++.
fuente
Aunque me gusta Linus Thorvalds, esta diatriba no tiene sustancia, solo una diatriba.
Si desea ver una queja sustantiva, aquí hay una: "Por qué C ++ es malo para el medio ambiente, causa el calentamiento global y mata a los cachorros" http://chaosradio.ccc.de/camp2007_m4v_1951.html Material adicional: http: // www .fefe.de / c ++ /
Una charla entretenida, en mi humilde opinión
fuente
STL y boost son portátiles, a nivel de código fuente. Supongo que de lo que habla Linus es de que C ++ carece de una ABI (interfaz binaria de aplicación). Por lo tanto, debe compilar todas las bibliotecas con las que se vincula, con la misma versión del compilador y con los mismos modificadores, o limítese al C ABI en las bibliotecas dll. También encuentro que annyoing ... pero a menos que esté haciendo bibliotecas de terceros, debería poder tomar el control de su entorno de compilación. Me parece que restringirme al C ABI no vale la pena. La conveniencia de poder pasar cadenas, vectores y punteros inteligentes de un dll a otro merece la pena tener que reconstruir todas las bibliotecas al actualizar compiladores o cambiar los conmutadores de compilador. Las reglas de oro que sigo son:
- Heredar para reutilizar la interfaz, no la implementación
-Preferir la agregación sobre la herencia
-Preferir, siempre que sea posible, funciones gratuitas a los métodos miembros
-Siempre use el lenguaje RAII para hacer que su código sea totalmente seguro. Evite intentar atrapar.
-Utilice punteros inteligentes, evite punteros desnudos (sin dueño)
-Preferir semántica de valor a semántica de referencia
-No reinventes la rueda, usa stl y boost
-Utilice el idioma Pimpl para ocultar privado y / o para proporcionar un firewall compilador
fuente
No poner una final
;
al final de una declaración de clase, al menos en algunas versiones de VC.fuente