significado embutido nas interfaces dos módulos

24

Considere o arquivo de cabeçalho:

class T
{
private:
  int const ID;

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

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

ou 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; }

Em um mundo de pré-módulos, esses cabeçalhos podem ser textualmente incluídos em várias TU sem violações de ODR. Além disso, como as funções-membro envolvidas são relativamente pequenas, o compilador provavelmente "inline" (evita chamadas de função ao usar) essas funções ou até otimiza algumas instâncias T.

Em um relatório recente sobre a reunião em que o C ++ 20 foi concluído, pude ler a seguinte declaração:

Esclarecemos o significado das inlineinterfaces do módulo: a intenção é que os corpos de funções que não são declarados explicitamente inlinenão façam parte da ABI de um módulo, mesmo que esses corpos de função apareçam na interface do módulo. Para dar aos autores do módulo mais controle sobre sua ABI, as funções-membro definidas nos corpos das classes nas interfaces dos módulos não são mais implícitas inline.

Não tenho certeza se não estou enganado. Isso significa que, em um mundo de módulos, para que o compilador seja capaz de otimizar chamadas de funções ausentes, precisamos anotá-las como inlinese fossem definidas em classe?

Em caso afirmativo, a seguinte interface do módulo seria equivalente aos cabeçalhos acima?

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; }
};

Mesmo que eu ainda não tenha um compilador com suporte para módulos, gostaria de começar a usá- inlinelo quando apropriado, para minimizar a refatoração futura.

metalfox
fonte

Respostas:

11

Isso significa que, em um mundo de módulos, para que o compilador seja capaz de otimizar chamadas de funções ausentes, precisamos anotá-las como inlinese fossem definidas em classe?

Até certo ponto.

Inlining é uma otimização "como se", e o inlining pode acontecer mesmo entre as unidades de tradução se o compilador for inteligente o suficiente.

Dito isto, inlining é mais fácil quando se trabalha em uma única unidade de tradução. Assim, para promover a inclusão fácil, uma inlinefunção declarada deve ter sua definição fornecida em qualquer unidade de tradução em que é usada. Isso não significa que o compilador certamente o incorporará (ou certamente não incorporará nenhuma inlinefunção não qualificada), mas facilita muito as coisas no processo de alinhamento, já que o alinhamento está acontecendo dentro de uma UT e não entre eles.

As definições de membro de classe definidas em uma classe, em um mundo pré-módulo, são declaradas inlineimplicitamente. Por quê? Porque a definição está dentro da classe. Em um mundo pré-módulo, as definições de classe que são compartilhadas entre as TUs são compartilhadas por inclusão textual. Os membros definidos em uma classe seriam, portanto, definidos no cabeçalho compartilhado entre essas TUs. Portanto, se várias TUs usarem a mesma classe, essas várias TUs o farão incluindo a definição de classe e a definição de seus membros declarada no cabeçalho.

Ou seja, você está incluindo a definição de qualquer maneira , por que não fazê-la inline?

Obviamente, isso significa que a definição de uma função agora faz parte do texto da classe. Se você alterar a definição de um membro declarado em um cabeçalho, isso força a recompilação de cada arquivo que inclui esse cabeçalho, recursivamente. Mesmo que a interface da classe em si não esteja mudando, você ainda precisará fazer uma recompilação. Portanto, criar essas funções implicitamente inlinenão altera isso, então você também pode fazer isso.

Para evitar isso em um mundo pré-módulo, você pode simplesmente definir o membro no arquivo C ++, que não será incluído em outros arquivos. Você perde o alinhamento fácil, mas ganha tempo de compilação.

Mas eis o seguinte: este é um artefato do uso da inclusão de texto como um meio de entregar uma classe a vários locais.

Em um mundo modular, você provavelmente desejaria definir cada função de membro dentro da própria classe, como vemos em outras linguagens como Java, C #, Python e similares. Isso mantém a localidade do código razoável e evita a necessidade de redigitar a mesma assinatura de função, atendendo às necessidades do DRY.

Mas se todos os membros forem definidos dentro da definição de classe, então, de acordo com as regras antigas, todos eles serão inline. E, para que um módulo permita uma função inline, o artefato do módulo binário precisaria incluir a definição dessas funções. O que significa que, sempre que você alterar uma única linha de código em uma definição de função, o módulo precisará ser construído, juntamente com todos os módulos que dependem dele, recursivamente.

A remoção de inlinemódulos implícitos fornece aos usuários os mesmos poderes que eles tinham nos dias de inclusão de texto, sem ter que mover a definição para fora da classe. Você pode escolher quais definições de função fazem parte do módulo e quais não.

Nicol Bolas
fonte
8

Isto vem do P1779 , adotado em Praga há alguns dias. Da proposta:

Este artigo propõe a remoção do status embutido implícito das funções definidas em uma definição de classe anexada a um módulo (nomeado). Isso permite que as classes se beneficiem de evitar declarações redundantes, mantendo a flexibilidade oferecida aos autores do módulo na declaração de funções com ou sem inline. Além disso, ele permite que amigos injetados de modelos de classe (que não podem ser definidos genericamente fora da definição de classe) não sejam totalmente inline. Ele também resolve o comentário NB US90 .

O jornal (entre outras coisas) removeu a frase:

Uma função definida dentro de uma definição de classe é uma função embutida.

e acrescentou a frase:

No módulo global, uma função definida em uma definição de classe está implicitamente embutida ([class.mfct], [class.friend]).


Seu exemplo com export module Mseria o equivalente modular do programa inicial. Observe que os compiladores já executam funções embutidas que não são anotadas inline, mas eles também usam a presença da inlinepalavra - chave em suas heurísticas.

Barry
fonte
Então, uma função em um módulo sem a inlinepalavra - chave nunca será incorporada pelo compilador, correto?
metalfox 18/02
11
@metalfox Não, não acredito que esteja correto.
Barry
11
Eu vejo. Obrigado. É como se fosse definido em um arquivo cpp, o que não significa necessariamente que não será incorporado no momento do link.
metalfox 19/02