¿Qué modismos de C ++ están en desuso en C ++ 11?

192

Con el nuevo estándar, hay nuevas formas de hacer las cosas, y muchas son más agradables que las viejas, pero la vieja forma todavía está bien. También está claro que el nuevo estándar no se desprecia oficialmente mucho, por razones de compatibilidad con versiones anteriores. Entonces la pregunta que queda es:

¿Qué formas antiguas de codificación son definitivamente inferiores a los estilos de C ++ 11, y qué podemos hacer ahora en su lugar?

Al responder esto, puede omitir las cosas obvias como "usar variables automáticas".

Alan Baljeu
fuente
13
No puedes desaprobar modismos.
Pubby
66
La charla de Herb Sutter en Going Native 2012 cubrió esto:
bames53
55
Ya no se recomienda devolver valores constantes. Obviamente auto_ptr, también está en desuso.
Kerrek SB
27
Por supuesto que puedes, Pubby. Antes de que se inventaran las plantillas de C ++, había una técnica macro para hacer plantillas. Luego C ++ los agregó, y la vieja forma se consideraba mala.
Alan Baljeu
77
Esta pregunta realmente debe trasladarse a Programmers.se.
Nicol Bolas

Respuestas:

173
  1. Clase final : C ++ 11 proporciona el finalespecificador para evitar la derivación de clase
  2. Las lambdas de C ++ 11 reducen sustancialmente la necesidad de clases de objeto de función con nombre (functor).
  3. Move Constructor : las formas mágicas en que las std::auto_ptrobras ya no son necesarias debido al soporte de primera clase para las referencias de valor.
  4. Safe bool : Esto se mencionó anteriormente. Los operadores explícitos de C ++ 11 obvian este lenguaje muy común de C ++ 03.
  5. Encogimiento : muchos contenedores STL C ++ 11 proporcionan unshrink_to_fit() función miembro, que debería eliminar la necesidad de intercambiar por una temporal.
  6. Clase base temporal : algunas bibliotecas antiguas de C ++ usan este lenguaje bastante complejo. Con la semántica de movimiento ya no se necesita.
  7. Las enumeraciones de tipo Safe Enum son muy seguras en C ++ 11.
  8. Prohibir la asignación de almacenamiento dinámico : la = deletesintaxis es una forma mucho más directa de decir que una funcionalidad particular se niega explícitamente. Esto es aplicable para evitar la asignación del montón (es decir, =deletepara el miembro operator new), evitar copias, asignación, etc.
  9. Typedef con plantillas : plantillas de alias en C ++ 11 reducen la necesidad de los typedefs simples con plantilla. Sin embargo, los generadores de tipo complejo aún necesitan funciones meta.
  10. Algunos cálculos numéricos en tiempo de compilación, como Fibonacci, se pueden reemplazar fácilmente mediante expresiones constantes generalizadas
  11. result_of: Los usos de la plantilla de clase result_ofdeben reemplazarse por decltype. Creo que result_ofusa decltypecuando está disponible.
  12. Los inicializadores de miembros en clase guardan la escritura para la inicialización predeterminada de miembros no estáticos con valores predeterminados.
  13. En el nuevo código C ++ 11 NULLdebe redefinirse como nullptr, pero vea la charla de STL para saber por qué decidieron no hacerlo.
  14. Los fanáticos de las plantillas de expresión están encantados de tener la sintaxis de la función de tipo de retorno final en C ++ 11. ¡No más tipos de devolución de 30 líneas!

¡Creo que me detendré allí!

Sumant
fuente
Gracias por las cosas detalladas!
Alan Baljeu
77
Gran respuesta, pero me gustaría sacar result_ofde la lista. A pesar de lo engorroso que se typenamenecesitaba antes, creo que a typename result_of<F(Args...)::typeveces puede ser más fácil de leer que decltype(std::declval<F>()(std::declval<Args>()...), y con la aceptación de N3436 en el documento de trabajo, ambos trabajan para SFINAE (que solía ser una ventaja de lo decltypeque result_ofno ofrecía)
Jonathan Wakely
Con respecto a 14) Todavía estoy llorando porque tengo que usar macros para escribir el mismo código dos veces, una para el cuerpo de la función y otra para la declaración decltype () ...
2
Me gustaría señalar que este tema está vinculado desde esta página de Microsoft como un artículo de "Para más información" en una introducción general al lenguaje C ++, ¡pero este tema es altamente especializado! ¿Puedo sugerir un breve "Este tema NO es para principiantes de C ++!" ¿Se incluirán consejos al comienzo del tema o esta respuesta?
Aacini
Re 12: "Inicialización de miembros en clase" - ese es el nuevo idioma, no un idioma obsoleto, ¿no? ¿Cambiar el orden de las oraciones quizás? Re 2: Los functores son muy útiles cuando desea pasar tipos en lugar de objetos (especialmente en parámetros de plantilla). Por lo tanto, solo algunos usos de los functores son obsoletos.
einpoklum
66

En un momento dado se argumentó que uno debería regresar por constvalor en lugar de solo por valor:

const A foo();
^^^^^

Esto fue mayormente inofensivo en C ++ 98/03, e incluso puede haber detectado algunos errores que parecían:

foo() = a;

Pero regresar constestá contraindicado en C ++ 11 porque inhibe la semántica del movimiento:

A a = foo();  // foo will copy into a instead of move into it

Así que relájate y codifica:

A foo();  // return by non-const value
Howard Hinnant
fuente
9
Sin embargo, los errores evitables ahora pueden detectarse mediante el uso de calificadores de referencia para las funciones. Tal como en el caso anterior, definir en A& operator=(A o)&lugar de A& operator=(A o). Estos evitan los errores tontos y hacen que las clases se comporten más como los tipos básicos y no evitan la semántica de movimiento.
Joe
61

¡Tan pronto como pueda abandonar 0y NULLen favor nullptr, hágalo!

En el código no genérico, el uso de 0o NULLno es tan importante. Pero tan pronto como comience a pasar constantes de puntero nulo en código genérico, la situación cambia rápidamente. Cuando pasa 0a un template<class T> func(T) Tse deduce como un inty no como un puntero nulo constante. Y no se puede convertir de nuevo a un puntero nulo constante después de eso. Esto cae en un atolladero de problemas que simplemente no existen si el universo solo se usa nullptr.

C ++ 11 no se desprecia 0y NULLcomo constantes de puntero nulo. Pero debe codificar como si lo hiciera.

Howard Hinnant
fuente
¿Qué es decltype (nullptr)?
44
@GrapschKnutsch: lo es std::nullptr_t.
Howard Hinnant
Sugiera que esto se reformule como el idioma en desuso en lugar de la nueva convención para adoptar (por ejemplo, "El uso de 0o NULLpara punteros nulos").
einpoklum
38

Safe bool idiomexplicit operator bool().

Constructores de copia privada (boost :: noncopyable) → X(const X&) = delete

Simulando la clase final con destructor privado y herencia virtualclass X final

kennytm
fuente
ejemplos buenos y concisos, uno de los cuales incluso lleva la palabra "modismo". bien puesto
Sebastian Mach
2
Wow, nunca antes había visto el 'idioma de bool seguro', ¡parece bastante desagradable! Espero que nunca lo necesite en el código anterior a C ++ 11 ...
boycy
24

Una de las cosas que simplemente hace que evite escribir algoritmos básicos en C ++ 11 es la disponibilidad de lambdas en combinación con los algoritmos proporcionados por la biblioteca estándar.

Los estoy usando ahora y es increíble la frecuencia con la que dices lo que quieres hacer usando count_if (), for_each () u otros algoritmos en lugar de tener que volver a escribir los malditos bucles.

Una vez que está utilizando un compilador de C ++ 11 con una biblioteca estándar completa de C ++ 11, ya no tiene una buena excusa para no usar algoritmos estándar para construir su propio . Lambda acaba de matarlo.

¿Por qué?

En la práctica (después de haber utilizado esta forma de escribir algoritmos yo mismo), es mucho más fácil leer algo que está construido con palabras sencillas que significan lo que se hace que con algunos bucles que hay que descifrar para saber el significado. Dicho esto, hacer que los argumentos lambda se deduzcan automáticamente ayudaría mucho a que la sintaxis sea más fácilmente comparable a un bucle sin formato.

Básicamente, los algoritmos de lectura hechos con algoritmos estándar son mucho más fáciles ya que las palabras ocultan los detalles de implementación de los bucles.

Supongo que solo debemos pensar en algoritmos de nivel superior ahora que tenemos algoritmos de nivel inferior para construir.

Klaim
fuente
8
En realidad hay una buena excusa. Estás utilizando los algoritmos de Boost.Range , que son mucho más agradables;)
Nicol Bolas
10
No veo que for_eachcon un lambda sea mejor que el bucle for basado en rango equivalente, con el contenido del lambda en el bucle. El código se ve más o menos igual, pero la lambda introduce algunos signos de puntuación adicionales. Puede usar equivalentes de cosas como boost::irangeaplicarlo a más bucles que solo aquellos que obviamente usan iteradores. Además, el bucle for basado en rango tiene una mayor flexibilidad, ya que puede salir temprano si es necesario (por returno por break), mientras que con la for_eachnecesidad de lanzar.
Steve Jessop
55
@SteveJessop: Aun así, la disponibilidad de un rango basado forhace que el it = c.begin(), const end = c.end(); it != end; ++itidioma habitual desaparezca.
Ben Voigt
77
@SteveJessop Una ventaja del for_eachalgoritmo sobre el rango basado en el bucle es que no puede break o noreturn . Es decir, cuando ves for_eachque sabes de inmediato sin mirar el cuerpo que no hay tal engaño.
bames53
55
@Klaim: para ser específico, lo comparo, por ejemplo, std::for_each(v.begin(), v.end(), [](int &i) { ++i; });con for (auto &i : v) { ++i; }. Acepto que la flexibilidad es de doble filo ( gotoes muy flexible, ese es el problema). No creo que la limitación de no poder utilizar breaken los for_eachcompensa de versión para el nivel de detalle adicional que exige - usuarios de for_eachaquí están sacrificando la OMI lectura real y conveniencia con una especie de noción teórica de que el for_eaches , en principio, más clara y conceptualmente más simple En la práctica no es más claro ni más simple.
Steve Jessop
10

Deberá implementar versiones personalizadas con swapmenos frecuencia. En C ++ 03, a swapmenudo es necesario un no-lanzamiento eficiente para evitar costosas y tirar copias, y dado que std::swapusa dos copias, a swapmenudo tiene que ser personalizado. En C ++, std::swaputiliza move, y por lo tanto, el enfoque cambia en la implementación de constructores de movimientos y operadores de asignación de movimientos eficientes y no arrojables. Dado que para estos el valor predeterminado es a menudo correcto, será mucho menos trabajo que en C ++ 03.

En general, es difícil predecir qué expresiones idiomáticas se utilizarán, ya que se crean a través de la experiencia. Podemos esperar un "C ++ 11 efectivo" tal vez el próximo año, y un "Estándares de codificación C ++ 11" solo en tres años porque la experiencia necesaria aún no existe.

Philipp
fuente
1
Dudo de esto. El estilo recomendado es usar swap para mover y copiar la construcción, pero no std :: swap porque sería circular.
Alan Baljeu
Sí, pero el constructor de movimientos generalmente llama a un intercambio personalizado, o es esencialmente equivalente.
Inverso
2

No sé el nombre, pero el código C ++ 03 a menudo usaba la siguiente construcción como reemplazo de la asignación de movimiento faltante:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Esto evitó cualquier copia debido a la elisión de copia combinada con lo swapanterior.

Andrzej
fuente
1
En su ejemplo, el intercambio no es necesario, copiar elisión construiría el valor de retorno de maptodos modos. La técnica que muestra es útil si mapya existe, en lugar de simplemente ser construida. El ejemplo sería mejor sin el comentario de "constructor predeterminado barato" y con "// ..." entre esa construcción y el intercambio
Jonathan Wakely
Lo cambié según su sugerencia. Gracias.
Andrzej
El uso de "grande" y "más grande" es confuso. ¿Por qué no explicar cómo importan los tamaños de la clave y el tipo de valor?
einpoklum
1

Cuando noté que un compilador que usa el estándar C ++ 11 ya no falla el siguiente código:

std::vector<std::vector<int>> a;

por supuestamente contener operador >>, comencé a bailar. En las versiones anteriores habría que hacer

std::vector<std::vector<int> > a;

Para empeorar las cosas, si alguna vez tuvo que depurar esto, ya sabe lo horrendos que son los mensajes de error que salen de esto.

Yo, sin embargo, no sé si esto fue "obvio" para usted.

v010dya
fuente
1
Esta característica ya se agregó en el C ++ anterior. O al menos Visual C ++ lo implementó por discusión de estándares muchos años antes.
Alan Baljeu
1
@AlanBaljeu Por supuesto, hay muchas cosas no estándar que se agregan al compilador / bibliotecas. Hubo toneladas de compiladores que tenían una declaración de variable "automática" antes de C ++ 11, pero no podía estar seguro de que su código pueda ser compilado por otra cosa. La pregunta era sobre el estándar, no sobre "¿había algún compilador que pudiera hacer esto".
v010dya
1

El retorno por valor ya no es un problema. Con la semántica de movimiento y / o la optimización del valor de retorno (dependiente del compilador), las funciones de codificación son más naturales sin gastos generales o costos (la mayoría de las veces).

Martin A
fuente
... pero qué idioma ha quedado en desuso?
einpoklum
No es una expresión idiomática, pero era una buena práctica que ya no se necesita. Incluso con el compilador soportado RVO que es opcional. en.wikipedia.org/wiki/Return_value_optimization "En las primeras etapas de la evolución de C ++, la incapacidad del lenguaje para devolver eficientemente un objeto de tipo de clase de una función se consideraba una debilidad ....." struct Data {char bytes [ dieciséis]; }; nulo f (Datos * p) {// generar resultado directamente en * p} int main () {Datos d; f (& d); }
Martin A
Estaba insinuando que debería expresar su respuesta como "la costumbre de evitar el retorno por valor ya no es relevante como etc., etc., etc."
einpoklum