¿Cuándo debo escribir la palabra clave 'en línea' para una función / método?

562

¿Cuándo debo escribir la palabra clave inlinepara una función / método en C ++?

Después de ver algunas respuestas, algunas preguntas relacionadas:

  • ¿Cuándo no debo escribir la palabra clave 'en línea' para una función / método en C ++?

  • ¿Cuándo no sabrá el compilador cuándo hacer una función / método 'en línea'?

  • ¿Importa si una aplicación es multiproceso cuando se escribe 'en línea' para una función / método?

Parcial
fuente
40
Si define una función en un encabezado, deberá declararla en línea. De lo contrario, obtendrá errores de vinculador sobre múltiples definiciones de la función.
Martin York
15
@ Martin: A menos que sea en una definición de clase, ser exigente.
David Thornley el
20
@David: para ser más exigente, es solo porque tales funciones están marcadas implícitamente inline(9.3 / 2).
Carreras de ligereza en órbita
Consulte también Funciones en línea en las Preguntas frecuentes de C ++. Tienen un muy buen tratamiento de inline.
jww

Respuestas:

882

Oh hombre, una de mis manías.

inlinees más statico externmenos una directiva que le dice al compilador que incorpore sus funciones. extern, static, inlineSon las directivas de vinculación, que se utilizan casi exclusivamente por el enlazador, no el compilador.

Se dice que le inlinesugiere al compilador que cree que la función debería estar en línea. Eso pudo haber sido cierto en 1998, pero una década después el compilador no necesita tales pistas. Sin mencionar que los humanos generalmente están equivocados cuando se trata de optimizar el código, por lo que la mayoría de los compiladores ignoran la 'pista'.

  • static- el nombre de la variable / función no se puede usar en otras unidades de traducción. Linker necesita asegurarse de que no utiliza accidentalmente una variable / función estáticamente definida de otra unidad de traducción.

  • extern- use este nombre de variable / función en esta unidad de traducción pero no se queje si no está definido. El vinculador lo resolverá y se asegurará de que todo el código que intentó utilizar algún símbolo externo tenga su dirección.

  • inline- esta función se definirá en varias unidades de traducción, no se preocupe por eso. El enlazador debe asegurarse de que todas las unidades de traducción usen una sola instancia de la variable / función.

Nota: En general, declarar plantillas no inlinetiene sentido, ya que ya tienen la semántica de enlace inline. Sin embargo, se requiere lainline especialización explícita y la creación de instancias de plantillas .


Respuestas específicas a sus preguntas:

  • ¿Cuándo debo escribir la palabra clave 'en línea' para una función / método en C ++?

    Solo cuando desee que la función se defina en un encabezado. Más exactamente solo cuando la definición de la función puede aparecer en múltiples unidades de traducción. Es una buena idea definir funciones pequeñas (como en un revestimiento) en el archivo de encabezado, ya que le da al compilador más información para trabajar mientras optimiza su código. También aumenta el tiempo de compilación.

  • ¿Cuándo no debo escribir la palabra clave 'en línea' para una función / método en C ++?

    No agregue en línea solo porque cree que su código se ejecutará más rápido si el compilador lo alinea.

  • ¿Cuándo no sabrá el compilador cuándo hacer una función / método 'en línea'?

    En general, el compilador podrá hacerlo mejor que usted. Sin embargo, el compilador no tiene la opción de codificar en línea si no tiene la definición de la función. En el código optimizado al máximo, generalmente todos los privatemétodos están en línea, ya sea que lo solicite o no.

    Como un aparte para evitar la inserción en GCC, use __attribute__(( noinline )), y en Visual Studio, use __declspec(noinline).

  • ¿Importa si una aplicación es multiproceso cuando se escribe 'en línea' para una función / método?

    El subprocesamiento múltiple no afecta la alineación de ninguna manera.

deft_code
fuente
172
+1 La mejor descripción de la línea que he visto en ... (para siempre). Ahora te estafaré y usaré esto en todas mis explicaciones de la palabra clave en línea.
Martin York el
66
@Ziggy, lo que intentaba decir era que la compilación en línea y la inlinepalabra clave no están relacionadas. Sin embargo, tienes la idea correcta. Como regla general, adivinar qué mejoraría si se incluyera en línea es muy propenso a errores. La excepción a esa regla es un revestimiento.
deft_code
44
Esta respuesta me confunde un poco. Dices todo eso acerca de que el compilador puede en línea / no en línea las cosas mejor. Luego dice que debe poner un liners / funciones pequeñas en el encabezado, y que el compilador no puede insertar código en línea sin la definición de la función. ¿No son estos un poco contradictorios? ¿Por qué no simplemente poner todo en el archivo cpp y dejar que el compilador decida?
user673679
55
El compilador solo incluirá llamadas de función en línea donde la definición esté disponible en el sitio de la llamada. Dejar todas las funciones en el archivo cpp limitaría la alineación a ese archivo. Sugiero definir líneas pequeñas en línea en .h ya que el costo de la velocidad de compilación es insignificante y está casi garantizado que el compilador incorporará la llamada. Mi punto sobre la compilación en línea es que es el puerto del arte negro de la optimización, en el que su compilador es mucho mejor que usted.
deft_code
8
Cada vez que leo algo sobre el conocimiento acumulativo de Internet, tengo que pensar en la famosa cita de John Lawton: la ironía de la era de la información es que le ha dado una nueva respetabilidad a la opinión desinformada.
Inspectable
60

Me gustaría contribuir a todas las excelentes respuestas en este hilo con un ejemplo convincente para dispersar cualquier malentendido restante.

Dados dos archivos de origen, como:

  • inline111.cpp:

    #include <iostream>
    
    void bar();
    
    inline int fun() {
      return 111;
    }
    
    int main() {
      std::cout << "inline111: fun() = " << fun() << ", &fun = " << (void*) &fun;
      bar();
    }
  • inline222.cpp:

    #include <iostream>
    
    inline int fun() {
      return 222;
    }
    
    void bar() {
      std::cout << "inline222: fun() = " << fun() << ", &fun = " << (void*) &fun;
    }

  • Caso A:

    Compilar :

    g++ -std=c++11 inline111.cpp inline222.cpp

    Salida :

    inline111: fun() = 111, &fun = 0x4029a0
    inline222: fun() = 111, &fun = 0x4029a0

    Discusión :

    1. Incluso si debe tener definiciones idénticas de sus funciones en línea, el compilador de C ++ no lo marca si ese no es el caso (en realidad, debido a la compilación por separado, no tiene formas de verificarlo). ¡Es su propio deber garantizar esto!

    2. Linker no se queja de la regla de una definición , como fun()se declara como inline. Sin embargo, debido a que inline111.cpp es la primera unidad de traducción (que realmente llama fun()) procesada por el compilador, el compilador crea fun()una instancia en su primer encuentro de llamada en inline111.cpp . Si el compilador decide no expandir fun()su llamada desde cualquier otro lugar de su programa ( por ejemplo, desde inline222.cpp ), la llamada a fun()siempre estará vinculada a su instancia producida desde inline111.cpp (la llamada a inline222.cppfun() interior )también puede producir una instancia en esa unidad de traducción, pero permanecerá desvinculada). De hecho, eso es evidente por las &fun = 0x4029a0impresiones idénticas .

    3. Finalmente, a pesar de la inlinesugerencia al compilador de expandir realmente el one-liner fun(), ignora su sugerencia por completo, lo que está claro porque fun() = 111en ambas líneas.


  • Caso B:

    Compilar (aviso de orden inverso) :

    g++ -std=c++11 inline222.cpp inline111.cpp

    Salida :

    inline111: fun() = 222, &fun = 0x402980
    inline222: fun() = 222, &fun = 0x402980

    Discusión :

    1. Este caso se afirma lo que se ha discutido en el caso A .

    2. Observe un punto importante, que si comenta la llamada real fun()en inline222.cpp ( por ejemplo , coutcomente la declaración completa en inline222.cpp ), entonces, a pesar del orden de compilación de sus unidades de traducción, fun()se instanciará en su primer encuentro de llamada en inline111.cpp , que da como resultado una impresión para el caso B como inline111: fun() = 111, &fun = 0x402980.


  • Caso C:

    Compilar (aviso -O2) :

    g++ -std=c++11 -O2 inline222.cpp inline111.cpp

    o

    g++ -std=c++11 -O2 inline111.cpp inline222.cpp

    Salida :

    inline111: fun() = 111, &fun = 0x402900
    inline222: fun() = 222, &fun = 0x402900

    Discusión :

    1. Como se describe aquí , la -O2optimización alienta al compilador a expandir realmente las funciones que se pueden incorporar (tenga en cuenta que también -fno-inlinees predeterminado sin opciones de optimización). Como es evidente por la impresión aquí, en fun()realidad se ha expandido en línea (de acuerdo con su definición en esa unidad de traducción en particular ), lo que resulta en dos impresiones diferentes fun() . A pesar de esto, todavía hay solo uno instancia vinculada globalmente fun()(como lo exige el estándar), como se desprende de una &fun impresión idéntica .
Alaroff
fuente
8
Su respuesta es una publicación ilustrativa de por qué el lenguaje hace que tales inlinefunciones sean comportamientos indefinidos.
R Sahu
También debe agregar casos en los que la compilación y la vinculación sean independientes, siendo cada .cppuna su propia unidad de traducción. Preferiblemente, agregue casos para -fltohabilitado / deshabilitado.
syockit el
La referencia de C ++ explícitamente dice "Si una función o variable en línea (desde C ++ 17) con enlace externo se define de manera diferente en diferentes unidades de traducción, el comportamiento es indefinido". Entonces, lo que escribiste es específico de GCC ya que es un efecto secundario de la orquestación de los procesos de compilación y vinculación. Además, tenga en cuenta que esto puede variar entre versiones.
Petr Fiedler
27

Todavía necesita alinear explícitamente su función al hacer la especialización de plantilla (si la especialización está en el archivo .h)

BostonLogan
fuente
21

1) Hoy en día, casi nunca. Si es una buena idea incorporar una función, el compilador lo hará sin su ayuda.

2) siempre. Ver # 1.

(Editado para reflejar que dividió su pregunta en dos preguntas ...)

Aric TenEyck
fuente
Si. La línea es solo una pista para el compilador, y es libre de ignorarlo. En estos días, el compilador probablemente sepa mejor que el programador qué funciones son las mejores para incorporar.
Mark Byers
1
Sí, pero es menos relevante: para que una función esté en línea, su cuerpo debe estar en la misma unidad de compilación (por ejemplo, en un encabezado). Eso es menos común en los programas C.
Michael Kohne el
1
definir una plantilla de función no miembro (también conocida como plantilla de función no estática) no requiere en línea. Ver una regla de definición (3.2 / 5).
deft_code el
2
-1: inlinetodavía es necesario, por ejemplo, para definir una función en un archivo de encabezado (y eso es necesario para incluir dicha función en varias unidades de compilación).
Melebius
1
@ Etienne que es específico de la implementación. Por norma, hay una regla de definición, lo que significa que si incluye ingenuamente la definición de la función en varias unidades de traducción, obtendrá un error. Pero si esa función tiene un inlineespecificador, sus instancias se colapsan automáticamente en una por el vinculador y no se usa ODR.
Ruslan
12

¿Cuándo no debo escribir la palabra clave 'en línea' para una función / método en C ++?

Si la función se declara en la cabecera y definido en el .cpparchivo, debería no escribir la palabra clave.

¿Cuándo no sabrá el compilador cuándo hacer una función / método 'en línea'?

No existe tal situación. El compilador no puede hacer una función en línea. Todo lo que puede hacer es alinear algunas o todas las llamadas a la función. No puede hacerlo si no tiene el código de la función (en ese caso, el enlazador debe hacerlo si es capaz de hacerlo).

¿Importa si una aplicación es multiproceso cuando se escribe 'en línea' para una función / método?

No, eso no importa en absoluto.

Johannes Schaub - litb
fuente
Hay casos en los que es apropiado usar en línea en un archivo .cpp. Por ejemplo, aplicar optimizaciones al código que es completamente específico de la implementación.
Robin Davies
@RobinDavies respuesta actualizada. Parece que entendiste mal lo que quería escribir.
Johannes Schaub - litb
5
  • ¿Cuándo no sabrá el compilador cuándo hacer una función / método 'en línea'?

Esto depende del compilador utilizado. No confíe ciegamente en que hoy en día los compiladores saben mejor que los humanos cómo conectarse en línea y nunca debe usarlo por razones de rendimiento, porque es una directiva de vinculación en lugar de una sugerencia de optimización. Si bien estoy de acuerdo en que estos argumentos son ideológicamente correctos, encontrar la realidad podría ser algo diferente.

Después de leer varios subprocesos, probé por curiosidad los efectos de la línea en el código que estoy trabajando y los resultados fueron que obtuve una aceleración medible para GCC y ninguna aceleración para el compilador Intel.

(Más detalles: simulaciones matemáticas con pocas funciones críticas definidas fuera de clase, GCC 4.6.3 (g ++ -O3), ICC 13.1.0 (icpc -O3); agregar en línea a los puntos críticos causó una aceleración de + 6% con el código GCC).

Entonces, si califica a GCC 4.6 como un compilador moderno, el resultado es que la directiva en línea sigue siendo importante si escribe tareas intensivas de CPU y sabe exactamente dónde está el cuello de botella.

meda beda
fuente
66
Me gustaría ver más evidencia para respaldar sus afirmaciones. Proporcione el código que está probando, así como la salida del ensamblador con y sin palabra clave en línea. Cualquier cantidad de cosas podría haberle dado beneficios de rendimiento.
void.pointer
1
Finalmente, alguien que no solo repite lo que otros dicen, sino que realmente verifica esas declaraciones. De hecho, Gcc todavía considera la palabra clave en línea como una pista (creo que clang la ignora por completo).
MikeMB
@ void.pointer: ¿Por qué es tan difícil de creer? Si los optimizadores ya eran perfectos, entonces las nuevas versiones no podrían mejorar el rendimiento del programa. Pero lo hacen regularmente.
MikeMB
3

En realidad, casi nunca. Todo lo que está haciendo es sugerir que el compilador realice una función dada en línea (por ejemplo, reemplace todas las llamadas a esta función / w su cuerpo). No hay garantías, por supuesto: el compilador puede ignorar la directiva.

El compilador generalmente hará un buen trabajo al detectar y optimizar cosas como esta.

DarkSquid
fuente
77
El problema es que inlinetiene una diferencia semántica en C ++ (por ejemplo, en la forma en que se tratan las definiciones múltiples), lo cual es importante en algunos casos (por ejemplo, plantillas).
Pavel Minaev
44
inline se usa para resolver casos en los que un símbolo tiene múltiples definiciones. Sin embargo, las plantillas ya son manejadas por el idioma. Una excepción es una función de plantilla especializada que ya no tiene parámetros de plantilla (plantilla <>). Estas se tratan más como funciones que como plantillas y, por lo tanto, necesitan la palabra clave en línea para vincularse.
deft_code el
2

gcc por defecto no integra ninguna función al compilar sin la optimización habilitada. No sé sobre visual studio - deft_code

Verifiqué esto para Visual Studio 9 (15.00.30729.01) compilando con / FAcs y mirando el código de ensamblaje: El compilador produjo llamadas a funciones miembro sin optimización habilitada en modo de depuración . Incluso si la función está marcada con __forceinline , no se genera ningún código de tiempo de ejecución en línea.

Jedzia
fuente
1
Habilitar / Muro para que se le
informe
0

Desea ponerlo al principio, antes de devolver el tipo. Pero la mayoría de los compiladores lo ignoran. Si está definido y tiene un bloque de código más pequeño, la mayoría de los compiladores lo consideran en línea de todos modos.

Jeremy Morgan
fuente
0

A menos que esté escribiendo una biblioteca o tenga razones especiales, puede olvidarse inliney utilizar la optimización del tiempo de enlace . Elimina el requisito de que una definición de función debe estar en un encabezado para que se considere su inclusión en las unidades de compilación, que es precisamente lo que inlinepermite.

(Pero vea ¿Hay alguna razón para no utilizar la optimización del tiempo de enlace? )

n.caillou
fuente
0

En línea palabra clave en solicita al compilador que reemplace la llamada a la función con el cuerpo de la función, primero evalúa la expresión y luego la pasa. argumentos

Cuándo usar:

  • Para mejorar el rendimiento
  • Para reducir la sobrecarga de llamadas.
  • Como es solo una solicitud al compilador, ciertas funciones no estarán en línea * funciones grandes
    • funciones que tienen demasiados argumentos condicionales
    • código recursivo y código con bucles, etc.
Sheetal
fuente
Puede ser beneficioso saber que este no es realmente el caso. El nivel de optimización -O0 a través de - Ofast es lo que determina si una función está en línea o no. Inline en la compilación regular (-O0) no alineará una función independientemente de si usa inlineo no en C y C ++. C en línea: stackoverflow.com/a/62287072/7194773 C ++ en línea: stackoverflow.com/a/62230963/7194773
Lewis Kelsey
0

C ++ en línea es totalmente diferente a C en línea .

#include <iostream>
extern inline int i[];
int i [5];
struct c {
  int function (){return 1;} //implicitly inline
  static inline int j = 3; //explicitly inline
};
int main() {
  c j;
  std::cout << i;
}

inlinepor sí solo afecta al compilador, al ensamblador y al enlazador. Es una directiva para el compilador que dice que solo emite un símbolo para esta función / datos si se usa en la unidad de traducción, y si es así, entonces, como los métodos de clase, dígale al ensamblador que los almacene en la sección .section .text.c::function(),"axG",@progbits,c::function(),comdato.section .bss.i,"awG",@nobits,i,comdat para los datos. Las instancias de plantilla también van en sus propios grupos de comdatos.

Esto sigue .section name, "flags"MG, @type, entsize, GroupName[, linkage]. Por ejemplo, el nombre de la sección es .text.c::function(). axGsignifica que la sección es asignable, ejecutable y en un grupo, es decir, se especificará un nombre de grupo (y no hay un indicador M, por lo que no se especificará ningún tamaño); @progbitssignifica que la sección contiene datos y no está en blanco; c::function()es el nombre del grupo y el grupo tienecomdatenlace, lo que significa que en todos los archivos de objetos, todas las secciones encontradas con este nombre de grupo etiquetado con comdat se eliminarán del ejecutable final, excepto 1, es decir, el compilador se asegura de que solo haya una definición en la unidad de traducción y luego le dice al ensamblador que coloque en su propio grupo en el archivo objeto (1 sección en 1 grupo) y luego el vinculador se asegurará de que si algún archivo objeto tiene un grupo con el mismo nombre, solo incluya uno en el archivo .exe final. La diferencia entre inliney no usar inlineahora es visible para el ensamblador y, como resultado, para el vinculador, porque el ensamblador no lo almacena de manera regular .datao .textetc. debido a sus directivas.

static inlineen una clase significa que es una definición de tipo y no una declaración (permite que se defina un miembro estático en la clase) y que sea en línea; ahora se comporta como arriba.

static inlineen el alcance del archivo solo afecta al compilador. Significa para el compilador: solo emitir un símbolo para esta función / datos si se usa en la unidad de traducción y hacerlo como un símbolo estático regular (almacenar in.text /.data sin la directiva .globl). Para el ensamblador ahora no hay diferencia entre staticystatic inline

extern inlinees una declaración que significa que debe definir este símbolo en la unidad de traducción o lanzar un error del compilador; si está definido, trátelo como regular inliney para el ensamblador y el enlazador no habrá diferencia entre extern inliney inline, por lo que este es solo un protector del compilador.

extern inline int i[];
extern int i[]; //allowed repetition of declaration with incomplete type, inherits inline property
extern int i[5]; //declaration now has complete type
extern int i[5]; //allowed redeclaration if it is the same complete type or has not yet been completed
extern int i[6]; //error, redeclaration with different complete type
int i[5]; //definition, must have complete type and same complete type as the declaration if there is a declaration with a complete type

Todo lo anterior sin la línea de error se contrae inline int i[5]. Obviamente, si lo hiciera extern inline int i[] = {5};, externsería ignorado debido a la definición explícita a través de la asignación.

inlineen un espacio de nombres, mira esto y esto

Lewis Kelsey
fuente
-1

Al desarrollar y depurar código, omita inline. Se complica la depuración.

La razón principal para agregarlos es para ayudar a optimizar el código generado. Por lo general, esto cambia el espacio de código aumentado por la velocidad, pero a veces inlineahorra espacio de código y tiempo de ejecución.

Gastar este tipo de pensamiento sobre la optimización del rendimiento antes de completar el algoritmo es una optimización prematura .

wallyk
fuente
12
inlinelas funciones generalmente no están integradas a menos que se compilen con optimizaciones, por lo que no afectan la depuración de ninguna manera. Recuerde que es una pista, no una demanda.
Pavel Minaev
3
gcc por defecto no integra ninguna función al compilar sin la optimización habilitada. No sé acerca de Visual Studio
deft_code
Trabajé en un enorme proyecto de g ++ que tenía habilitada la depuración. Quizás otras opciones lo impidieron, pero las inlinefunciones estaban alineadas. Era imposible establecer un punto de interrupción significativo en ellos.
wallyk
2
habilitar la depuración no deja de incluir en línea en gcc. Si se habilitó alguna optimización (-O1 o superior), entonces gcc intentará alinear los casos más obvios. Tradicionalmente, GDB ha tenido dificultades con los puntos de interrupción y los constructores, especialmente los constructores en línea. Pero eso se ha solucionado en versiones recientes (al menos 6.7, quizás antes).
deft_code el
2
Agregar inlineno hará nada para mejorar el código en un compilador moderno, que puede determinar si está en línea o no por sí solo.
David Thornley el
-1

Cuando uno debe en línea:

1.Cuando se quiere evitar la sobrecarga de cosas que suceden cuando se llama a la función, como pasar parámetros, transferir controles, devolver controles, etc.

2. La función debe ser pequeña, con frecuencia llamada y hacer que la función en línea sea realmente ventajosa, ya que según la regla 80-20, intente hacer que la función esté en línea, lo que tiene un impacto importante en el rendimiento del programa.

Como sabemos, inline es solo una solicitud de compilación similar al registro y le costará el tamaño del código de objeto.

Ashish
fuente
"inline es solo una solicitud de compilación similar al registro" Son similares porque tampoco lo son las solicitudes ni tienen nada que ver con la optimización. inlineha perdido su estado como una pista de optimización, y la mayoría de los compiladores solo lo usan para tener en cuenta las definiciones múltiples, como deberían hacerlo en mi opinión. Más aún, desde C ++ 11, registerha quedado en desuso por su significado anterior de 'Sé mejor que el compilador cómo optimizar': ahora es solo una palabra reservada sin significado actual.
underscore_d
@underscore_d: Gcc todavía escucha inlinehasta cierto punto.
MikeMB
-1

La función en línea de C ++ es un concepto poderoso que se usa comúnmente con las clases. Si una función está en línea, el compilador coloca una copia del código de esa función en cada punto donde se llama a la función en tiempo de compilación.

Cualquier cambio en una función en línea podría requerir que todos los clientes de la función se vuelvan a compilar porque el compilador necesitaría reemplazar todo el código una vez más, de lo contrario, continuará con la funcionalidad anterior.

Para alinear una función, coloque la palabra clave en línea antes del nombre de la función y defina la función antes de realizar cualquier llamada a la función. El compilador puede ignorar el calificador en línea en caso de que la función definida sea más que una línea.

Una definición de función en una definición de clase es una definición de función en línea, incluso sin el uso del especificador en línea.

El siguiente es un ejemplo, que utiliza la función en línea para devolver un máximo de dos números

#include <iostream>

using namespace std;

inline int Max(int x, int y) { return (x > y)? x : y; }

// Main function for the program
int main() {
   cout << "Max (100,1010): " << Max(100,1010) << endl;

   return 0;
}

Para más información ver aquí .

amirfg
fuente