significado en línea en las interfaces del módulo

24

Considere el archivo de encabezado:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept : ID(ID_) {}

  int GetID() const noexcept { return ID; }
};

o alternativamente:

class T
{
private:
  int const ID;

public:
  explicit T(int const ID_) noexcept;

  int GetID() const noexcept;
};

inline T::T(int const ID_) noexcept : ID(ID_) {}

inline int T::GetID() const noexcept { return ID; }

En un mundo previo a los módulos, estos encabezados pueden incluirse textualmente en múltiples TU sin infracciones de ODR. Además, dado que las funciones miembro involucradas son relativamente pequeñas, es probable que el compilador "en línea" (evite las llamadas a funciones al usar) esas funciones, o incluso optimice algunas instancias por Tcompleto.

En un informe reciente sobre la reunión donde se terminó C ++ 20, pude leer la siguiente declaración:

Aclaramos el significado de las inlineinterfaces en el módulo: la intención es que los cuerpos de funciones que no se declaran explícitamente inlineno sean parte de la ABI de un módulo, incluso si esos cuerpos de funciones aparecen en la interfaz del módulo. Para dar a los autores de módulos más control sobre su ABI, las funciones miembro definidas en los cuerpos de clase en las interfaces de módulos ya no están implícitamente inline.

No estoy seguro de no estar equivocado. ¿Eso significa que, en un mundo de módulos, para que el compilador pueda optimizar las llamadas a funciones, tenemos que anotarlas como inlinesi estuvieran definidas en clase?

Si es así, ¿la siguiente interfaz del módulo sería equivalente a los encabezados anteriores?

export module M;

export
class T
{
private:
  int const ID;

public:
  inline explicit T(int const ID_) noexcept : ID(ID_) {}

  inline int GetID() const noexcept { return ID; }
};

Aunque todavía no tengo un compilador con soporte de módulos, me gustaría comenzar a usarlo inlineasí cuando sea apropiado, para minimizar la futura refactorización.

metalfox
fuente

Respuestas:

11

¿Eso significa que, en un mundo de módulos, para que el compilador pueda optimizar las llamadas a funciones, tenemos que anotarlas como inlinesi estuvieran definidas en clase?

Hasta cierto grado.

La alineación es una optimización "como si", y la alineación puede ocurrir incluso entre unidades de traducción si el compilador es lo suficientemente inteligente.

Dicho esto, la alineación es más fácil cuando se trabaja dentro de una sola unidad de traducción. Por lo tanto, para promover una línea fácil, una inlinefunción declarada debe tener su definición provista en cualquier unidad de traducción donde se use. Esto no significa que el compilador ciertamente lo inlinealineará (o ciertamente no alineará ninguna función no calificada), pero hace que las cosas sean mucho más fáciles en el proceso de alineación, ya que la alineación ocurre dentro de una TU en lugar de entre ellas.

Las definiciones de miembros de clase definidas dentro de una clase, en un mundo previo al módulo, se declaran inlineimplícitamente. ¿Por qué? Porque la definición está dentro de la clase. En un mundo previo al módulo, las definiciones de clase que se comparten entre las TU se comparten mediante inclusión textual. Por lo tanto, los miembros definidos en una clase se definirían en el encabezado compartido entre esas TU. Entonces, si varias TU usan la misma clase, esas múltiples TU lo hacen al incluir la definición de clase y la definición de sus miembros declarados en el encabezado.

Es decir, está incluyendo la definición de todos modos , ¿por qué no hacerlo inline?

Por supuesto, esto significa que la definición de una función ahora es parte del texto de la clase. Si cambia la definición de un miembro declarado en un encabezado, esto obliga a la recompilación de cada archivo que incluye ese encabezado, de forma recursiva. Incluso si la interfaz de la clase en sí no está cambiando, aún necesita hacer una compilación. Entonces, hacer tales funciones implícitamente inlineno cambia esto, por lo que es mejor que lo hagas.

Para evitar esto en un mundo previo al módulo, simplemente puede definir el miembro en el archivo C ++, que no se incluirá en otros archivos. Pierde la línea fácil, pero gana tiempo de compilación.

Pero aquí está la cosa: este es un artefacto del uso de la inclusión textual como un medio para impartir una clase a múltiples lugares.

En un mundo modular, es probable que desee definir cada función miembro dentro de la propia clase, como vemos en otros lenguajes como Java, C #, Python y similares. Esto mantiene la localidad del código razonable y evita tener que volver a escribir la misma firma de función, satisfaciendo así las necesidades de DRY.

Pero si todos los miembros se definen dentro de la definición de clase, entonces, según las reglas anteriores, todos esos miembros lo serían inline. Y para que un módulo permita que una función sea inline, el artefacto del módulo binario debería incluir la definición de esas funciones. Lo que significa que cada vez que cambie incluso una línea de código en una definición de función de este tipo, el módulo tendrá que construirse, junto con cada módulo dependiendo de él, de forma recursiva.

Al eliminar los inlinemódulos implícitos, los usuarios tienen los mismos poderes que tenían en los días de inclusión textual, sin tener que mover la definición fuera de la clase. Puede elegir qué definiciones de funciones forman parte del módulo y cuáles no.

Nicol Bolas
fuente
8

Esto viene de P1779 , recién adoptado en Praga hace unos días. De la propuesta:

Este documento propone eliminar el estado implícito en línea de las funciones definidas en una definición de clase adjunta a un módulo (con nombre). Esto permite que las clases se beneficien al evitar declaraciones redundantes, manteniendo la flexibilidad ofrecida a los autores de módulos al declarar funciones con o sin línea. Además, permite que los amigos inyectados de las plantillas de clase (que no se pueden definir genéricamente fuera de la definición de clase) no estén en línea. También resuelve el comentario NB US90 .

El documento (entre otras cosas) eliminó la oración:

Una función definida dentro de una definición de clase es una función en línea.

y agregó la oración:

En el módulo global, una función definida dentro de una definición de clase está implícitamente en línea ([class.mfct], [class.friend]).


Su ejemplo con export module Msería el equivalente modular del programa inicial. Tenga en cuenta que los compiladores ya realizan funciones en línea que no están anotadas inline, es solo que además utilizan la presencia de la inlinepalabra clave en sus heurísticas.

Barry
fuente
Entonces, una función en un módulo sin la inlinepalabra clave nunca será alineada por el compilador, ¿correcto?
metalfox
1
@metalfox No, no creo que sea correcto.
Barry
1
Veo. Gracias. Es como se definió en un archivo cpp, lo que no necesariamente significa que no estará en línea en el momento del enlace.
metalfox