O que são agregados e PODs e como / por que eles são especiais?


548

Esta FAQ é sobre agregados e PODs e abrange o seguinte material:

  • O que são agregados ?
  • O que são PODs (dados antigos simples)?
  • Como eles estão relacionados?
  • Como e por que eles são especiais?
  • O que muda para o C ++ 11?


Pode-se dizer que a motivação por trás dessas definições é aproximadamente: POD == memcpy'able, Aggregate == agregate-initializable?
Ofek Shilon 31/03

Respostas:


572

Como ler:

Este artigo é bastante longo. Se você quiser saber sobre agregados e PODs (dados antigos simples), reserve um tempo e leia-o. Se você estiver interessado apenas em agregados, leia apenas a primeira parte. Se você estiver interessado apenas em PODs, deverá primeiro ler a definição, implicações e exemplos de agregados e, em seguida, poderá pular para PODs, mas eu ainda recomendo a leitura da primeira parte na íntegra. A noção de agregados é essencial para a definição de PODs. Se você encontrar algum erro (mesmo menor, incluindo gramática, estilística, formatação, sintaxe etc.), deixe um comentário, eu o edito.

Esta resposta se aplica ao C ++ 03. Para outros padrões C ++, consulte:

O que são agregados e por que são especiais

Definição formal do padrão C ++ ( C ++ 03 8.5.1 §1 ) :

Um agregado é uma matriz ou uma classe (cláusula 9) sem construtores declarados pelo usuário (12.1), sem membros de dados não estáticos privados ou protegidos (cláusula 11), sem classes base (cláusula 10) e sem funções virtuais (10.3 )

Então, tudo bem, vamos analisar essa definição. Primeiro de tudo, qualquer matriz é um agregado. Uma classe também pode ser agregada se ... espere! nada é dito sobre estruturas ou sindicatos, eles não podem ser agregados? Sim eles podem. Em C ++, o termo classrefere-se a todas as classes, estruturas e uniões. Portanto, uma classe (ou estrutura ou união) é agregada se, e somente se, atender aos critérios das definições acima. O que esses critérios implicam?

  • Isso não significa que uma classe agregada não possa ter construtores, na verdade, pode ter um construtor padrão e / ou um construtor de cópias, desde que declarados implicitamente pelo compilador e não explicitamente pelo usuário.

  • Nenhum membro de dados não estático privado ou protegido . Você pode ter quantas funções membro privadas e protegidas (mas não construtores), bem como muitos membros de dados estáticos privados ou protegidos e funções membro conforme desejar e não violar as regras para classes agregadas

  • Uma classe agregada pode ter um operador e / ou destrutor de atribuição de cópia declarado / definido pelo usuário

  • Uma matriz é agregada, mesmo que seja uma matriz do tipo de classe não agregada.

Agora vamos ver alguns exemplos:

class NotAggregate1
{
  virtual void f() {} //remember? no virtual functions
};

class NotAggregate2
{
  int x; //x is private by default and non-static 
};

class NotAggregate3
{
public:
  NotAggregate3(int) {} //oops, user-defined constructor
};

class Aggregate1
{
public:
  NotAggregate1 member1;   //ok, public member
  Aggregate1& operator=(Aggregate1 const & rhs) {/* */} //ok, copy-assignment  
private:
  void f() {} // ok, just a private function
};

Você entendeu a ideia. Agora vamos ver como os agregados são especiais. Diferentemente de classes não agregadas, elas podem ser inicializadas com chaves {}. Essa sintaxe de inicialização é comumente conhecida por matrizes, e acabamos de aprender que elas são agregadas. Então, vamos começar com eles.

Type array_name[n] = {a1, a2, …, am};

se (m == n)
o i- ésimo elemento da matriz é inicializado com um i
else se (m <n)
os primeiros m elementos da matriz são inicializados com 1 , 2 ,…, am e os outrosn - melementos são, se possível, inicializados por valor (veja abaixo a explicação do termo);
caso contrário, (m> n)
o compilador emitirá
outro erro (esse é o caso quando n não for especificado de maneira alguma int a[] = {1, 2, 3};)
o tamanho de a matriz (n) é assumida como igual a m, portantoint a[] = {1, 2, 3};é equivalente aint a[3] = {1, 2, 3};

Quando um objecto de tipo escalar ( bool, int, char, double, pontos, etc.) é inicializado-valor , isso significa que é inicializado com 0para que tipo ( falsepara bool, 0.0por double, etc). Quando um objeto do tipo classe com um construtor padrão declarado pelo usuário é inicializado por valor, seu construtor padrão é chamado. Se o construtor padrão for definido implicitamente, todos os membros não estáticos serão recursivamente inicializados por valor. Essa definição é imprecisa e um pouco incorreta, mas deve fornecer a ideia básica. Uma referência não pode ser inicializada por valor. A inicialização de valor para uma classe não agregada pode falhar se, por exemplo, a classe não tiver um construtor padrão apropriado.

Exemplos de inicialização de matriz:

class A
{
public:
  A(int) {} //no default constructor
};
class B
{
public:
  B() {} //default constructor available
};
int main()
{
  A a1[3] = {A(2), A(1), A(14)}; //OK n == m
  A a2[3] = {A(2)}; //ERROR A has no default constructor. Unable to value-initialize a2[1] and a2[2]
  B b1[3] = {B()}; //OK b1[1] and b1[2] are value initialized, in this case with the default-ctor
  int Array1[1000] = {0}; //All elements are initialized with 0;
  int Array2[1000] = {1}; //Attention: only the first element is 1, the rest are 0;
  bool Array3[1000] = {}; //the braces can be empty too. All elements initialized with false
  int Array4[1000]; //no initializer. This is different from an empty {} initializer in that
  //the elements in this case are not value-initialized, but have indeterminate values 
  //(unless, of course, Array4 is a global array)
  int array[2] = {1, 2, 3, 4}; //ERROR, too many initializers
}

Agora vamos ver como as classes agregadas podem ser inicializadas com chaves. Praticamente da mesma maneira. Em vez dos elementos da matriz, inicializaremos os membros de dados não estáticos na ordem em que aparecem na definição de classe (todos eles são públicos por definição). Se houver menos inicializadores do que membros, o restante será inicializado por valor. Se for impossível inicializar com valor um dos membros que não foram explicitamente inicializados, obteremos um erro em tempo de compilação. Se houver mais inicializadores do que o necessário, também obteremos um erro em tempo de compilação.

struct X
{
  int i1;
  int i2;
};
struct Y
{
  char c;
  X x;
  int i[2];
  float f; 
protected:
  static double d;
private:
  void g(){}      
}; 

Y y = {'a', {10, 20}, {20, 30}};

No exemplo acima, y.cé inicializado com 'a', y.x.i1com 10, y.x.i2com 20, y.i[0]com 20, y.i[1]com 30e y.fé inicializado por valor, ou seja, inicializado com 0.0. O membro estático protegido dnão é inicializado, porque é static.

As uniões agregadas são diferentes, pois você pode inicializar apenas o primeiro membro com chaves. Eu acho que se você é avançado o suficiente em C ++ para considerar o uso de uniões (o uso deles pode ser muito perigoso e deve ser considerado com cuidado), você mesmo pode procurar as regras para uniões no padrão :).

Agora que sabemos o que há de especial em agregados, vamos tentar entender as restrições nas classes; ou seja, por que eles estão lá. Devemos entender que a inicialização de membros com chaves implica que a classe nada mais é do que a soma de seus membros. Se um construtor definido pelo usuário estiver presente, isso significa que o usuário precisa executar algum trabalho extra para inicializar os membros, portanto, a inicialização entre chaves estaria incorreta. Se houver funções virtuais, significa que os objetos desta classe têm (na maioria das implementações) um ponteiro para a chamada vtable da classe, que é definida no construtor, portanto, a inicialização entre chaves seria insuficiente. Você pode descobrir o restante das restrições de maneira semelhante a um exercício :).

O suficiente sobre os agregados. Agora podemos definir um conjunto mais estrito de tipos, ou seja, PODs

O que são PODs e por que são especiais

Definição formal do padrão C ++ ( C ++ 03 9 §4 ) :

Uma estrutura POD é uma classe agregada que não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz de tais tipos) ou referência e não possui operador de atribuição de cópia definido pelo usuário nem destruidor definido pelo usuário. Da mesma forma, uma união POD é uma união agregada que não possui membros de dados não estáticos do tipo estrutura não POD, união POD (ou matriz de tais tipos) ou referência e não possui operador de atribuição de cópia definido pelo usuário e nenhum destruidor definido pelo usuário. Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.

Uau, esse é mais difícil de analisar, não é? :) Vamos deixar os sindicatos de fora (pelo mesmo motivo acima) e reformular de uma maneira um pouco mais clara:

Uma classe agregada é chamada de POD se não tiver um operador e destruidor de atribuição de cópia definido pelo usuário e nenhum de seus membros não estáticos for uma classe não-POD, matriz de não-POD ou uma referência.

O que essa definição implica? (Eu mencionei POD significa Plain Old Data ?)

  • Todas as classes POD são agregadas ou, em outras palavras, se uma classe não é agregada, é certo que não é uma POD
  • Classes, assim como estruturas, podem ser PODs, mesmo que o termo padrão seja POD-struct para ambos os casos
  • Assim como no caso de agregados, não importa que membros estáticos a classe tenha

Exemplos:

struct POD
{
  int x;
  char y;
  void f() {} //no harm if there's a function
  static std::vector<char> v; //static members do not matter
};

struct AggregateButNotPOD1
{
  int x;
  ~AggregateButNotPOD1() {} //user-defined destructor
};

struct AggregateButNotPOD2
{
  AggregateButNotPOD1 arrOfNonPod[3]; //array of non-POD class
};

Classes POD, uniões POD, tipos escalares e matrizes desses tipos são chamados coletivamente de tipos POD.
Os PODs são especiais de várias maneiras. Vou fornecer apenas alguns exemplos.

  • As classes POD são as mais próximas às estruturas C. Diferentemente deles, os PODs podem ter funções membro e membros estáticos arbitrários, mas nenhum desses dois altera o layout da memória do objeto. Portanto, se você deseja escrever uma biblioteca dinâmica mais ou menos portátil que possa ser usada do C e até do .NET, tente fazer com que todas as suas funções exportadas tomem e retornem apenas parâmetros dos tipos POD.

  • A vida útil dos objetos do tipo não-POD começa quando o construtor termina e termina quando o destruidor termina. Para as classes POD, a vida útil começa quando o armazenamento do objeto é ocupado e termina quando esse armazenamento é liberado ou reutilizado.

  • Para objetos de tipos POD, é garantido pelo padrão que, quando você memcpycolocar o conteúdo do seu objeto em uma matriz de caracteres ou caracteres não assinados, e depois memcpyo conteúdo voltar ao objeto, o objeto manterá seu valor original. Observe que não existe essa garantia para objetos de tipos não POD. Além disso, você pode copiar objetos POD com segurança memcpy. O exemplo a seguir assume que T é do tipo POD:

    #define N sizeof(T)
    char buf[N];
    T obj; // obj initialized to its original value
    memcpy(buf, &obj, N); // between these two calls to memcpy,
    // obj might be modified
    memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type
    // holds its original value
  • declaração goto. Como você deve saber, é ilegal (o compilador deve emitir um erro) saltar via goto de um ponto em que alguma variável ainda não estava no escopo para um ponto em que já está no escopo. Esta restrição se aplica apenas se a variável for do tipo não POD. No exemplo a seguir f()está mal formado, enquanto g()está bem formado. Observe que o compilador da Microsoft é muito liberal com essa regra - apenas envia um aviso nos dois casos.

    int f()
    {
      struct NonPOD {NonPOD() {}};
      goto label;
      NonPOD x;
    label:
      return 0;
    }
    
    int g()
    {
      struct POD {int i; char c;};
      goto label;
      POD x;
    label:
      return 0;
    }
  • É garantido que não haverá preenchimento no início de um objeto POD. Em outras palavras, se um POD de classe de um primeiro membro é do tipo T, você pode com segurança reinterpret_casta partir A*de T*e obter o ponteiro para o primeiro membro e vice-versa.

A lista continua e continua…

Conclusão

É importante entender o que exatamente é um POD, porque muitos recursos de linguagem, como você vê, se comportam de maneira diferente para eles.


3
Boa resposta. Comentários: "Se o construtor padrão for definido implicitamente, todos os membros não estáticos serão recursivamente inicializados por valor". e "A inicialização do valor para uma classe não agregada pode falhar se, por exemplo, a classe não tiver um construtor padrão apropriado". não está correto: A inicialização do valor de uma classe com um construtor padrão declarado implicitamente não requer um construtor padrão definido implicitamente. Assim, dado (insira private:conforme apropriado): struct A { int const a; };então A()está bem formado, mesmo que Aa definição padrão do construtor esteja mal formada.
Johannes Schaub - litb

4
@ Kev: Se você conseguir agrupar as mesmas informações em uma resposta mais curta, todos nós gostaríamos de votar novamente!
SBI

3
O @Armen também nota que você pode fazer várias respostas para a mesma pergunta. Cada resposta pode conter parte da solução da pergunta. Parafuso que coisa-mark aceitou, na minha opinião :)
Johannes Schaub - litb

3
A resposta é ótima. Ainda revisito esse post algumas vezes. A propósito, avisos para o Visual Studio. A "instrução goto" para o pod é ignorada pelo compilador MSVC, como você mencionou. Mas, para a instrução switch / case, gera erro de compilação. Baseado nesse conceito, fiz alguns verificadores de pod de teste: stackoverflow.com/questions/12232766/test-for-pod-ness-in-c-c11/…
bruziuz

2
No ponto inicial que começa com "A vida útil dos objetos do tipo de classe não POD começa quando o construtor termina e termina quando o destruidor termina". a última parte deveria dizer "quando o destruidor iniciar".
Quokka

457

O que muda para o C ++ 11?

Agregados

A definição padrão de um agregado mudou um pouco, mas ainda é praticamente a mesma:

Um agregado é uma matriz ou uma classe (Cláusula 9) sem construtores fornecidos pelo usuário (12.1), sem chaves ou inicializadores iguais para membros de dados não estáticos (9.2), sem membros de dados não estáticos privados ou protegidos ( Cláusula 11), sem classes base (Cláusula 10) e sem funções virtuais (10.3).

Ok, o que mudou?

  1. Anteriormente, um agregado não podia ter construtores declarados pelo usuário , mas agora não pode ter construtores fornecidos pelo usuário . Existe alguma diferença? Sim, existe, porque agora você pode declarar construtores e padronizá- los:

    struct Aggregate {
        Aggregate() = default; // asks the compiler to generate the default implementation
    };

    Isso ainda é agregado porque um construtor (ou qualquer função de membro especial) padrão da primeira declaração não é fornecido pelo usuário.

  2. Agora, um agregado não pode ter inicializadores entre parênteses ou iguais para membros de dados não estáticos. O que isto significa? Bem, isso é apenas porque, com esse novo padrão, podemos inicializar membros diretamente na classe assim:

    struct NotAggregate {
        int x = 5; // valid in C++11
        std::vector<int> s{1,2,3}; // also valid
    };

    O uso desse recurso faz com que a classe não seja mais agregada, porque é basicamente equivalente a fornecer seu próprio construtor padrão.

Portanto, o que é um agregado não mudou muito. Ainda é a mesma idéia básica, adaptada aos novos recursos.

E os PODs?

Os PODs passaram por muitas mudanças. Muitas regras anteriores sobre PODs foram relaxadas nesse novo padrão e a maneira como a definição é fornecida no padrão foi radicalmente alterada.

A idéia de um POD é capturar basicamente duas propriedades distintas:

  1. Ele suporta inicialização estática e
  2. Compilar um POD em C ++ fornece o mesmo layout de memória que uma estrutura compilada em C.

Por esse motivo , a definição foi dividida em dois conceitos distintos: classes triviais e classes de layout padrão , porque são mais úteis que o POD. O padrão agora raramente usa o termo POD, preferindo os conceitos triviais e de layout padrão mais específicos .

A nova definição basicamente diz que um POD é uma classe que é trivial e tem layout padrão, e essa propriedade deve ser mantida recursivamente para todos os membros de dados não estáticos:

Uma estrutura POD é uma classe sem união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz desses tipos). Da mesma forma, uma união POD é uma união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não-POD, união não-POD (ou matriz de tais tipos). Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.

Vamos examinar cada uma dessas duas propriedades em detalhes separadamente.

Classes triviais

Trivial é a primeira propriedade mencionada acima: classes triviais suportam inicialização estática. Se uma classe é trivialmente copiável (um superconjunto de classes triviais), não há problema em copiar sua representação sobre o local com coisas como memcpye esperar que o resultado seja o mesmo.

O padrão define uma classe trivial da seguinte maneira:

Uma classe trivialmente copiável é uma classe que:

- não possui construtores de cópias não triviais (12.8),

- não possui construtores de movimentos não triviais (12.8),

- não possui operadores de atribuição de cópias não triviais (13.5.3, 12.8),

- não possui operadores de atribuição de movimento não triviais (13.5.3, 12.8), e

- possui um destruidor trivial (12.4).

Uma classe trivial é uma classe que possui um construtor padrão trivial (12.1) e é trivialmente copiável.

[ Nota: Em particular, uma classe trivialmente copiável ou trivial não possui funções virtuais ou classes base virtuais. - end note ]

Então, o que são todas essas coisas triviais e não triviais?

Um construtor de copiar / mover para a classe X é trivial se não for fornecido pelo usuário e se

- a classe X não possui funções virtuais (10.3) e nenhuma classe base virtual (10.1), e

- o construtor selecionado para copiar / mover cada subobjeto direto da classe base é trivial, e

- para cada membro de dados não estático de X que é do tipo de classe (ou matriz do mesmo), o construtor selecionado para copiar / mover esse membro é trivial;

caso contrário, o construtor copiar / mover não é trivial.

Basicamente, isso significa que um construtor de copiar ou mover é trivial se não for fornecido pelo usuário, a classe não possui nada virtual e essa propriedade é válida recursivamente para todos os membros da classe e para a classe base.

A definição de um operador de atribuição trivial de copiar / mover é muito semelhante, simplesmente substituindo a palavra "construtor" por "operador de atribuição".

Um destruidor trivial também tem uma definição semelhante, com a restrição adicional de que não pode ser virtual.

E ainda existe outra regra semelhante para construtores padrão triviais, com a adição de que um construtor padrão não é trivial se a classe tiver membros de dados não estáticos com inicializadores de chaves ou iguais , como vimos acima.

Aqui estão alguns exemplos para esclarecer tudo:

// empty classes are trivial
struct Trivial1 {};

// all special members are implicit
struct Trivial2 {
    int x;
};

struct Trivial3 : Trivial2 { // base class is trivial
    Trivial3() = default; // not a user-provided ctor
    int y;
};

struct Trivial4 {
public:
    int a;
private: // no restrictions on access modifiers
    int b;
};

struct Trivial5 {
    Trivial1 a;
    Trivial2 b;
    Trivial3 c;
    Trivial4 d;
};

struct Trivial6 {
    Trivial2 a[23];
};

struct Trivial7 {
    Trivial6 c;
    void f(); // it's okay to have non-virtual functions
};

struct Trivial8 {
     int x;
     static NonTrivial1 y; // no restrictions on static members
};

struct Trivial9 {
     Trivial9() = default; // not user-provided
      // a regular constructor is okay because we still have default ctor
     Trivial9(int x) : x(x) {};
     int x;
};

struct NonTrivial1 : Trivial3 {
    virtual void f(); // virtual members make non-trivial ctors
};

struct NonTrivial2 {
    NonTrivial2() : z(42) {} // user-provided ctor
    int z;
};

struct NonTrivial3 {
    NonTrivial3(); // user-provided ctor
    int w;
};
NonTrivial3::NonTrivial3() = default; // defaulted but not on first declaration
                                      // still counts as user-provided
struct NonTrivial5 {
    virtual ~NonTrivial5(); // virtual destructors are not trivial
};

Layout padrão

O layout padrão é a segunda propriedade. O padrão menciona que eles são úteis para a comunicação com outros idiomas, e isso ocorre porque uma classe de layout padrão tem o mesmo layout de memória da estrutura ou união C equivalente.

Essa é outra propriedade que deve conter recursivamente para membros e todas as classes base. E, como sempre, nenhuma função virtual ou classe base virtual é permitida. Isso tornaria o layout incompatível com C.

Uma regra simples aqui é que as classes de layout padrão devem ter todos os membros de dados não estáticos com o mesmo controle de acesso. Anteriormente estes tiveram que ser tudo público , mas agora você pode fazê-los privados ou protegidos, desde que eles são tudo privado ou tudo protegido.

Ao usar herança, apenas uma classe na árvore de herança inteira pode ter membros de dados não estáticos, e o primeiro membro de dados não estáticos não pode ser do tipo de classe base (isso pode violar as regras de alias), caso contrário, não é um padrão. classe de layout.

É assim que a definição é apresentada no texto padrão:

Uma classe de layout padrão é uma classe que:

- não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,

- não possui funções virtuais (10.3) e nenhuma classe base virtual (10.1),

- possui o mesmo controle de acesso (cláusula 11) para todos os membros de dados não estáticos,

- não possui classes básicas de layout fora do padrão,

- não possui membros de dados não estáticos na classe mais derivada e no máximo uma classe base com membros de dados não estáticos ou não possui classes de base com membros de dados não estáticos, e

- não possui classes base do mesmo tipo que o primeiro membro de dados não estático.

Uma estrutura de layout padrão é uma classe de layout padrão definida com a estrutura de chave de classe ou a classe de chave de classe.

Uma união de layout padrão é uma classe de layout padrão definida com a união de chave de classe.

[ Nota: As classes de layout padrão são úteis para se comunicar com o código escrito em outras linguagens de programação. Seu layout é especificado em 9.2. - end note ]

E vamos ver alguns exemplos.

// empty classes have standard-layout
struct StandardLayout1 {};

struct StandardLayout2 {
    int x;
};

struct StandardLayout3 {
private: // both are private, so it's ok
    int x;
    int y;
};

struct StandardLayout4 : StandardLayout1 {
    int x;
    int y;

    void f(); // perfectly fine to have non-virtual functions
};

struct StandardLayout5 : StandardLayout1 {
    int x;
    StandardLayout1 y; // can have members of base type if they're not the first
};

struct StandardLayout6 : StandardLayout1, StandardLayout5 {
    // can use multiple inheritance as long only
    // one class in the hierarchy has non-static data members
};

struct StandardLayout7 {
    int x;
    int y;
    StandardLayout7(int x, int y) : x(x), y(y) {} // user-provided ctors are ok
};

struct StandardLayout8 {
public:
    StandardLayout8(int x) : x(x) {} // user-provided ctors are ok
// ok to have non-static data members and other members with different access
private:
    int x;
};

struct StandardLayout9 {
    int x;
    static NonStandardLayout1 y; // no restrictions on static members
};

struct NonStandardLayout1 {
    virtual f(); // cannot have virtual functions
};

struct NonStandardLayout2 {
    NonStandardLayout1 X; // has non-standard-layout member
};

struct NonStandardLayout3 : StandardLayout1 {
    StandardLayout1 x; // first member cannot be of the same type as base
};

struct NonStandardLayout4 : StandardLayout3 {
    int z; // more than one class has non-static data members
};

struct NonStandardLayout5 : NonStandardLayout3 {}; // has a non-standard-layout base class

Conclusão

Com essas novas regras, muito mais tipos podem ser PODs agora. E mesmo que um tipo não seja POD, podemos tirar proveito de algumas das propriedades POD separadamente (se for apenas uma de layout trivial ou padrão).

A biblioteca padrão possui características para testar essas propriedades no cabeçalho <type_traits>:

template <typename T>
struct std::is_pod;
template <typename T>
struct std::is_trivial;
template <typename T>
struct std::is_trivially_copyable;
template <typename T>
struct std::is_standard_layout;

2
você pode elaborar as seguintes regras: a) as classes de layout padrão devem ter todos os membros de dados não estáticos com o mesmo controle de acesso; b) apenas uma classe na árvore de herança inteira pode ter membros de dados não estáticos, e o primeiro membro de dados não estáticos não pode ser de um tipo de classe base (isso pode violar as regras de alias). Especialmente quais são as razões para eles? Para a regra posterior, você pode fornecer um exemplo de quebra de alias?
Andriy Tylychko

@ AndyT: Veja minha resposta. Tentei responder da melhor maneira possível.
Nicol Bolas

5
Talvez você queira atualizar isso para o C ++ 14, que removeu o requisito "sem chaves ou inicializadores iguais" para agregados.
TC

@TC obrigado pelo aviso. Vou procurar essas alterações em breve e atualizá-las.
R. Martinho Fernandes

1
Com relação ao alias: existe uma regra de layout C ++ que se uma classe C tiver uma base X (vazia) e o primeiro membro de dados de C for do tipo X, esse primeiro membro não poderá ter o mesmo deslocamento da base X; ele recebe um byte de preenchimento fictício à frente, se necessário, para evitar isso. Ter duas instâncias de X (ou subclasse) no mesmo endereço pode quebrar coisas que precisam distinguir instâncias diferentes por meio de seus endereços (uma instância vazia não tem mais nada para distingui-la ...). Em qualquer caso, a necessidade de inserir esse byte de preenchimento quebra 'layout compatível'.
Greggo

106

O que mudou para o C ++ 14

Podemos nos referir ao padrão Draft C ++ 14 para referência.

Agregados

Isso é abordado na seção 8.5.1 Agregados, que nos fornece a seguinte definição:

Um agregado é uma matriz ou uma classe (Cláusula 9) sem construtores fornecidos pelo usuário (12.1), sem membros de dados não estáticos privados ou protegidos (Cláusula 11), sem classes base (Cláusula 10) e sem funções virtuais (10.3 )

A única alteração agora é adicionar inicializadores de membros da classe não tornar uma classe não agregada. Portanto, o exemplo a seguir da inicialização agregada do C ++ 11 para classes com inicializadores in-pace de membros :

struct A
{
  int a = 3;
  int b = 3;
};

não era um agregado no C ++ 11, mas está no C ++ 14. Essa alteração é abordada no N3605: Inicializadores e agregados de membros , com o seguinte resumo:

Bjarne Stroustrup e Richard Smith levantaram uma questão sobre a inicialização agregada e os inicializadores de membros não trabalhando juntos. Este artigo propõe corrigir o problema adotando a redação proposta por Smith que remove uma restrição que os agregados não podem ter inicializadores de membros.

POD permanece o mesmo

A definição para estrutura POD ( dados antigos simples ) é abordada na seção 9 Classes, que diz:

Uma estrutura POD 110 é uma classe de não união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não POD, união não POD (ou matriz de tais tipos). Da mesma forma, uma união POD é uma união que é uma classe trivial e uma classe de layout padrão e não possui membros de dados não estáticos do tipo estrutura não-POD, união não-POD (ou matriz desses tipos). Uma classe POD é uma classe que é uma estrutura POD ou uma união POD.

que tem a mesma redação do C ++ 11.

Alterações de layout padrão para C ++ 14

Conforme observado no pod de comentários, a definição de layout padrão foi alterada para o C ++ 14, mas isso ocorreu por meio de relatórios de defeitos que foram aplicados ao C ++ 14 após o fato.

Havia três DRs:

Portanto , o layout padrão partiu deste Pre C ++ 14:

Uma classe de layout padrão é uma classe que:

  • (7.1) não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
  • (7.2) não possui funções virtuais ([class.virtual]) e nenhuma classe base virtual ([class.mi]),
  • (7.3) tem o mesmo controle de acesso (Cláusula [class.access]) para todos os membros de dados não estáticos,
  • (7.4) não possui classes base de layout fora do padrão,
  • (7.5) não possui membros de dados não estáticos na classe mais derivada e no máximo uma classe base com membros de dados não estáticos ou não possui classes de base com membros de dados não estáticos e
  • (7.6) não possui classes de base do mesmo tipo que o primeiro membro de dados não estático.109

Para isso no C ++ 14 :

Uma classe S é uma classe de layout padrão se:

  • (3.1) não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
  • (3.2) não possui funções virtuais nem classes base virtuais,
  • (3.3) tem o mesmo controle de acesso para todos os membros de dados não estáticos,
  • (3.4) não possui classes base de layout fora do padrão,
  • (3.5) tem no máximo um subobjeto de classe base de qualquer tipo,
  • (3.6) possui todos os membros de dados não estáticos e campos de bits na classe e suas classes base declaradas pela primeira vez na mesma classe, e
  • (3.7) não possui nenhum elemento do conjunto M (S) dos tipos como classe base, sendo que para qualquer tipo X, M (X) é definido da seguinte forma.104 [Nota: M (X) é o conjunto dos tipos de todos os subobjetos que não são da classe base que podem estar com um deslocamento zero no X. - nota final]
    • (3.7.1) Se X for um tipo de classe sem união, sem membros de dados não estáticos (possivelmente herdados), o conjunto M (X) estará vazio.
    • (3.7.2) Se X é um tipo de classe não-união com um membro de dados não estático do tipo X0 que é de tamanho zero ou é o primeiro membro de dados não estático de X (em que o membro pode ser uma união anônima ), o conjunto M (X) consiste em X0 e nos elementos de M (X0).
    • (3.7.3) Se X é um tipo de união, o conjunto M (X) é a união de todos os M (Ui) e o conjunto que contém todos os Ui, em que cada Ui é o tipo do i-ésimo membro de dados não estático de X .
    • (3.7.4) Se X é um tipo de matriz com o tipo de elemento Xe, o conjunto M (X) consiste em Xe e nos elementos de M (Xe).
    • (3.7.5) Se X for do tipo sem classe e sem matriz, o conjunto M (X) estará vazio.

4
Há uma proposta para permitir que os agregados de ter uma classe base, desde que ele é padrão construível ver N4404
Shafik Yaghmour

enquanto o POD pode permanecer o mesmo, o C ++ 14 StandardLayoutType, que é um requisito para o POD, foi alterado de acordo com cppref: en.cppreference.com/w/cpp/named_req/StandardLayoutType
Ciro Santilli

1
@CiroSantilli 法轮功 改造 中心 六四 事件 法轮功 obrigado, não sei como senti falta deles, vou tentar atualizar nos próximos dias.
Shafik Yaghmour

Deixe-me saber se você pode criar um exemplo que seja POD em C ++ 14, mas não em C ++ 11 :-) Comecei uma lista detalhada de exemplos em: stackoverflow.com/questions/146452/what- are-pod-types-in-c /…
Ciro Santilli escreveu:

1
@CiroSantilli 法轮功 改造 中心 六四 事件 法轮功, então o que aconteceu aqui é se olharmos para a descrição do layout padrão no C ++ 11 e C ++ 14 que eles correspondem. Essas alterações foram aplicadas por meio de relatórios de defeitos no C ++ 14. Então, quando eu escrevi isso originalmente, estava correto :-p
Shafik Yaghmour

47

você pode elaborar as seguintes regras:

Vou tentar:

a) as classes de layout padrão devem ter todos os membros de dados não estáticos com o mesmo controle de acesso

Isso é simples: todos os membros de dados não-estáticos devem todos ser public, privateou protected. Você não pode ter alguns publice alguns private.

O raciocínio para eles vai para o raciocínio por ter uma distinção entre "layout padrão" e "layout não padrão". Ou seja, para dar ao compilador a liberdade de escolher como colocar as coisas na memória. Não se trata apenas de ponteiros vtable.

Quando eles padronizaram o C ++ em 98, eles precisavam basicamente prever como as pessoas o implementariam. Embora tivessem um pouco de experiência em implementação com vários tipos de C ++, eles não estavam certos sobre as coisas. Então eles decidiram ser cautelosos: dê aos compiladores o máximo de liberdade possível.

É por isso que a definição de POD no C ++ 98 é tão rigorosa. Deu aos compiladores C ++ uma grande latitude no layout dos membros para a maioria das classes. Basicamente, os tipos de POD destinavam-se a casos especiais, algo que você escreveu especificamente por um motivo.

Quando o C ++ 11 estava sendo trabalhado, eles tinham muito mais experiência com compiladores. E eles perceberam que ... os escritores do compilador C ++ são realmente preguiçosos. Eles tinham toda essa liberdade, mas eles não fazem nada com ele.

As regras do layout padrão são mais ou menos codificadoras da prática comum: a maioria dos compiladores realmente não precisou mudar muito, se é que havia alguma coisa para implementá-las (fora de talvez algumas coisas para os traços de tipo correspondentes).

Agora, quando se trata de public/ private, as coisas são diferentes. A liberdade de reordenar quais membros são publicvs. privaterealmente pode ser importante para o compilador, principalmente nas compilações de depuração. E como o ponto do layout padrão é que há compatibilidade com outros idiomas, não é possível que o layout seja diferente na depuração versus versão.

Depois, há o fato de que realmente não prejudica o usuário. Se você estiver criando uma classe encapsulada, é bem provável que todos os seus membros de dados sejam privateassim. Geralmente, você não expõe membros de dados públicos em tipos totalmente encapsulados. Portanto, isso seria apenas um problema para aqueles poucos usuários que desejam fazer isso, que desejam essa divisão.

Portanto, não é uma grande perda.

b) apenas uma classe em toda a árvore de herança pode ter membros de dados não estáticos,

A razão para este é o motivo pelo qual eles padronizaram o layout padrão novamente: prática comum.

Não prática comum quando se trata de ter dois membros de uma árvore de herança que realmente armazenam coisas. Alguns colocam a classe base antes da derivada, outros fazem o contrário. De que maneira você ordena os membros se eles vierem de duas classes base? E assim por diante. Os compiladores divergem bastante sobre essas questões.

Além disso, graças à regra zero / um / infinito, quando você disser que pode ter duas classes com membros, poderá dizer quantas quiser. Isso requer adicionar muitas regras de layout para lidar com isso. Você tem que dizer como a herança múltipla funciona, que classes colocam seus dados antes de outras classes, etc. São muitas regras, para pouquíssimo ganho material.

Você não pode criar tudo o que não possui funções virtuais e um layout padrão de construtor padrão.

e o primeiro membro de dados não estático não pode ser de um tipo de classe base (isso pode violar regras de alias).

Eu realmente não posso falar com este. Não sou educado o suficiente nas regras de alias do C ++ para realmente entender. Mas isso tem algo a ver com o fato de o membro base compartilhar o mesmo endereço que a própria classe base. Isso é:

struct Base {};
struct Derived : Base { Base b; };

Derived d;
static_cast<Base*>(&d) == &d.b;

E isso provavelmente é contra as regras de alias do C ++. De algum modo.

No entanto, considere isto: o quão útil podia ter a capacidade de fazer isso nunca realmente ser? Como apenas uma classe pode ter membros de dados não estáticos, Deriveddeve ser essa classe (já que ela possui um Basecomo membro). Portanto, Base deve estar vazio (de dados). E se Baseestiver vazio, assim como uma classe base ... por que um membro de dados?

Como Baseestá vazio, não possui estado. Portanto, qualquer função membro não estática fará o que fizer com base em seus parâmetros, não em seu thisponteiro.

Então, novamente: sem grandes perdas.


Obrigado pela explicação, isso ajuda muito. Provavelmente apesar static_cast<Base*>(&d)e &d.bsão do mesmo Base*tipo, eles apontam para coisas diferentes, quebrando a regra de alias. Por favor me corrija.
Andriy Tylychko

1
e, por que se apenas uma classe pode ter membros de dados não estáticos, Deriveddeve ser essa classe?
Andriy Tylychko

3
@ Andy: Para que Derivedo primeiro membro de sua classe seja sua classe base, ele deve ter duas coisas: uma classe base e um membro . E como apenas uma classe na hierarquia pode ter membros (e ainda ter layout padrão), isso significa que sua classe base não pode ter membros.
Nicol Bolas

3
@ AndyT, Sim, você está essencialmente correto, IME, sobre a regra de alias. São necessárias duas instâncias distintas do mesmo tipo para ter endereços de memória distintos. (Isso permite o rastreamento da identidade do objeto com endereços de memória.) O objeto base e o primeiro membro derivado são instâncias diferentes; portanto, eles devem ter endereços diferentes, o que força o preenchimento a ser adicionado, afetando o layout da classe. Se eles fossem de tipos diferentes, não importaria; objetos com tipos diferentes podem ter o mesmo endereço (uma classe e seu primeiro membro de dados, por exemplo).
Adam H. Peterson

46

Alterações no C ++ 17

Faça o download do rascunho final do Padrão Internacional C ++ 17 aqui .

Agregados

O C ++ 17 expande e aprimora agregados e inicialização agregada. A biblioteca padrão agora também inclui uma std::is_aggregateclasse de característica de tipo. Aqui está a definição formal das seções 11.6.1.1 e 11.6.1.2 (referências internas elididas):

Um agregado é uma matriz ou uma classe com
- sem construtores fornecidos pelo usuário, explícitos ou herdados
- sem membros de dados não estáticos privados ou protegidos,
- sem funções virtuais e
- sem classes de base virtuais, privadas ou protegidas.
[Nota: A inicialização agregada não permite acessar membros ou construtores da classe base protegida e privada. —End note]
Os elementos de um agregado são:
- para uma matriz, os elementos da matriz em ordem crescente de subscrito ou
- para uma classe, as classes base diretas em ordem de declaração, seguidas pelos membros de dados não estáticos diretos que não são membros de uma união anônima, em ordem de declaração.

O que mudou?

  1. Agora, os agregados podem ter classes básicas públicas e não virtuais. Além disso, não é um requisito que as classes base sejam agregadas. Se não forem agregados, serão inicializados em lista.
struct B1 // not a aggregate
{
    int i1;
    B1(int a) : i1(a) { }
};
struct B2
{
    int i2;
    B2() = default;
};
struct M // not an aggregate
{
    int m;
    M(int a) : m(a) { }
};
struct C : B1, B2
{
    int j;
    M m;
    C() = default;
};
C c { { 1 }, { 2 }, 3, { 4 } };
cout
    << "is C aggregate?: " << (std::is_aggregate<C>::value ? 'Y' : 'N')
    << " i1: " << c.i1 << " i2: " << c.i2
    << " j: " << c.j << " m.m: " << c.m.m << endl;

//stdout: is C aggregate?: Y, i1=1 i2=2 j=3 m.m=4
  1. Construtores padrão explícitos não são permitidos
struct D // not an aggregate
{
    int i = 0;
    D() = default;
    explicit D(D const&) = default;
};
  1. Herdeiros construtores não são permitidos
struct B1
{
    int i1;
    B1() : i1(0) { }
};
struct C : B1 // not an aggregate
{
    using B1::B1;
};


Classes triviais

A definição de classe trivial foi reformulada no C ++ 17 para solucionar vários defeitos que não foram abordados no C ++ 14. As mudanças foram de natureza técnica. Aqui está a nova definição em 12.0.6 (referências internas elididas):

Uma classe trivialmente copiável é uma classe:
- onde cada construtor de cópia, construtor de movimentação, operador de atribuição de cópia e operador de atribuição de movimentação é excluído ou trivial,
- que possui pelo menos um construtor de cópia não excluído, construtor de movimentação, operador de atribuição de cópia, ou mova o operador de atribuição e
- que tenha um destruidor trivial e não excluído.
Uma classe trivial é uma classe que é trivialmente copiável e possui um ou mais construtores padrão, todos triviais ou excluídos e pelo menos um dos quais não é excluído. [Nota: Em particular, uma classe trivialmente copiável ou trivial não possui funções virtuais ou classes base virtuais. - nota final]

Alterar:

  1. No C ++ 14, para que uma classe seja trivial, a classe não pode ter nenhum operador copiar / mover construtor / atribuição que não seja trivial. No entanto, um construtor / operador declarado implicitamente padrão pode ser não trivial e, no entanto, definido como excluído porque, por exemplo, a classe continha um subobjeto do tipo de classe que não podia ser copiado / movido. A presença desse construtor / operador não trivial, definido como excluído, faria com que toda a classe não fosse trivial. Um problema semelhante existia com destruidores. O C ++ 17 esclarece que a presença de tais construtores / operadores não faz com que a classe não seja trivialmente copiável, portanto não trivial, e que uma classe trivialmente copiável deve ter um destruidor trivial e não excluído. DR1734 , DR1928
  2. O C ++ 14 permitiu que uma classe trivialmente copiável, portanto uma classe trivial, tivesse todos os operadores de construção / atribuição de cópia / movimentação declarados como excluídos. Se tal classe também tiver um layout padrão, ele poderá, no entanto, ser copiado / movido legalmente std::memcpy. Isso era uma contradição semântica, porque, ao definir como excluídos todos os operadores de construtor / atribuição, o criador da classe claramente pretendia que a classe não pudesse ser copiada / movida, mas a classe ainda atendia à definição de uma classe trivialmente copiável. Portanto, no C ++ 17, temos uma nova cláusula declarando que a classe trivialmente copiável deve ter pelo menos um operador construtor / atribuição de cópia / movimentação trivial e não excluído (embora não necessariamente acessível ao público). Veja N4148 , DR1734
  3. A terceira mudança técnica diz respeito a um problema semelhante com os construtores padrão. No C ++ 14, uma classe poderia ter construtores padrão triviais que foram implicitamente definidos como excluídos, e ainda assim ser uma classe trivial. A nova definição esclarece que uma classe trivial deve ter pelo menos um construtor padrão trivial e não excluído. Veja DR1496

Classes de layout padrão

A definição de layout padrão também foi reformulada para tratar dos relatórios de defeitos. Mais uma vez, as mudanças foram de natureza técnica. Aqui está o texto do padrão (12.0.7). Como antes, as referências internas são elididas:

Uma classe S é uma classe de layout padrão se:
- não possui membros de dados não estáticos do tipo classe de layout não padrão (ou matriz de tais tipos) ou referência,
- não possui funções virtuais nem classes base virtuais,
- possui o mesmo controle de acesso para todos os membros de dados não estáticos,
- não possui classes base de layout não padrão,
- possui no máximo uma subobjeto de classe base de qualquer tipo,
- possui todos os membros de dados não estáticos e campos de bits em a classe e suas classes base declaradas pela primeira vez na mesma classe e
- não possui nenhum elemento do conjunto M (S) dos tipos (definidos abaixo) como classe base.108
M (X) é definido da seguinte forma:
- Se X é um tipo de classe sem união sem ( membros de dados não estáticos possivelmente herdados), o conjunto M (X) está vazio.
- Se X é um tipo de classe não-união cujo primeiro membro de dados não estáticos possui o tipo X0 (onde o referido membro pode ser uma união anônima), o conjunto M (X) consiste em X0 e nos elementos de M (X0).
- Se X é um tipo de união, o conjunto M (X) é a união de todos os M (Ui) e o conjunto contendo todas as Ui, em que cada Ui é o tipo do i-ésimo membro de dados não estáticos de X.
- Se X é um tipo de matriz com o tipo de elemento Xe, o conjunto M (X) consiste em Xe e nos elementos de M (Xe).
- Se X é do tipo sem classe e sem matriz, o conjunto M (X) está vazio.
[Nota: M (X) é o conjunto dos tipos de todos os subobjetos que não são da classe base que são garantidos em uma classe de layout padrão como tendo um deslocamento zero em X. - finalizar nota]
[Exemplo:

struct B { int i; }; // standard-layout class
struct C : B { }; // standard-layout class
struct D : C { }; // standard-layout class
struct E : D { char : 4; }; // not a standard-layout class
struct Q {};
struct S : Q { };
struct T : Q { };
struct U : S, T { }; // not a standard-layout class

108 exemplo ) assegura que dois subobjetos que tenham o mesmo tipo de classe e pertençam ao mesmo objeto mais derivado não sejam alocados no mesmo endereço.

Alterar:

  1. Esclareceu que o requisito de que apenas uma classe na árvore de derivação "tenha" membros de dados não estáticos refere-se a uma classe em que esses membros de dados são declarados primeiro, não classes em que possam ser herdados e estendeu esse requisito a campos de bits não estáticos . Também esclareceu que uma classe de layout padrão "tem no máximo um subobjeto de classe base de qualquer tipo". Veja DR1813 , DR1881
  2. A definição de layout padrão nunca permitiu que o tipo de qualquer classe base fosse do mesmo tipo que o primeiro membro de dados não estático. É para evitar uma situação em que um membro de dados no deslocamento zero tenha o mesmo tipo que qualquer classe base. O padrão C ++ 17 fornece uma definição mais rigorosa e recursiva do "conjunto de tipos de todos os subobjetos que não são da classe base que são garantidos em uma classe de layout padrão com um deslocamento zero" para proibir esses tipos de ser o tipo de qualquer classe base. Veja DR1672 , DR2120 .

Nota: O comitê de padrões do C ++ pretendeu que as alterações acima, com base nos relatórios de defeitos, fossem aplicadas ao C ++ 14, embora o novo idioma não esteja no padrão C ++ 14 publicado. Está no padrão C ++ 17.


Nota: Acabei de atualizar minha resposta. Os defeitos de alterações de layout padrão têm status CD4, o que significa que eles são realmente aplicados ao C ++ 14. Foi por isso que minha resposta não os incluiu porque isso aconteceu depois que eu escrevi minha resposta.
Shafik Yaghmour

Note, eu comecei uma recompensa nessa questão.
Shafik Yaghmour

Obrigado @ShafikYaghmour. Analisarei o status dos relatórios de defeitos e modificarei minha resposta de acordo.
ThomasMcLeod

@ ShafikYaghmour, Depois de uma revisão do processo C ++ 14 e me parece que, enquanto esses DRs foram "aceitos" na reunião do Rapperswil em junho de 2014, o idioma da reunião de Issaquah de fevereiro de 2014 foi o que se tornou C ++ 14. Consulte isocpp.org/blog/2014/07/trip-report-summer-iso-c-meeting "de acordo com as regras da ISO, não aprovamos formalmente nenhuma edição do documento de trabalho do C ++". Estou esquecendo de algo?
`` #

Eles têm o status 'CD4', o que significa que devem ser aplicados no modo C ++ 14.
Shafik Yaghmour

14

O que muda em

Seguindo o resto do tema claro desta questão, o significado e o uso de agregados continuam a mudar com todos os padrões. Existem várias mudanças importantes no horizonte.

Tipos com construtores declarados pelo usuário P1008

No C ++ 17, esse tipo ainda é um agregado:

struct X {
    X() = delete;
};

E, portanto, X{}ainda compila porque é a inicialização agregada - não uma invocação de construtor. Consulte também: Quando um construtor privado não é um construtor privado?

No C ++ 20, a restrição mudará de exigir:

nenhum explicitconstrutor fornecido pelo usuário ou herdado

para

nenhum construtor declarado ou herdado pelo usuário

Isso foi adotado no rascunho de trabalho do C ++ 20 . Nem a questão Xaqui nem a Cquestão vinculada serão agregadas em C ++ 20.

Isso também cria um efeito ioiô com o seguinte exemplo:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

No C ++ 11/14, nãoB era um agregado devido à classe base, portanto, executa a inicialização de valor que chama quais chamadas , em um ponto em que é acessível. Isso foi bem formado.B{}B::B()A::A()

No C ++ 17, Btornou-se agregado porque as classes base eram permitidas, o que fez B{}a inicialização agregada. Isso requer a inicialização da lista de cópias e Ade {}, mas fora do contexto de B, onde não está acessível. No C ++ 17, isso é mal formado (auto x = B(); seria bom).

No C ++ 20 agora, devido à alteração de regra acima, Bmais uma vez deixa de ser um agregado (não por causa da classe base, mas por causa do construtor padrão declarado pelo usuário - mesmo que seja o padrão). Então, voltamos ao Bconstrutor de, e esse trecho se torna bem formado.

Inicializando agregados a partir de uma lista de valores entre parênteses P960

Um problema comum que surge é querer usar emplace()construtores -style com agregados:

struct X { int a, b; };
std::vector<X> xs;
xs.emplace_back(1, 2); // error

Isso não funciona, porque emplacetentará efetivamente executar a inicialização X(1, 2), que não é válida. A solução típica é adicionar um construtor a X, mas com esta proposta (atualmente trabalhando no Core), os agregados terão efetivamente sintetizados construtores que fazem a coisa certa - e se comportam como construtores regulares. O código acima será compilado como está no C ++ 20.

Dedução de Argumento de Modelo de Classe (CTAD) para Agregados P1021 (especificamente P1816 )

No C ++ 17, isso não compila:

template <typename T>
struct Point {
    T x, y;
};

Point p{1, 2}; // error

Os usuários precisariam escrever seu próprio guia de dedução para todos os modelos agregados:

template <typename T> Point(T, T) -> Point<T>;

Mas como isso é, em certo sentido, "a coisa mais óbvia" a ser feita, e é basicamente apenas um clichê, a linguagem fará isso por você. Este exemplo será compilado no C ++ 20 (sem a necessidade do guia de dedução fornecido pelo usuário).


Embora eu vá votar, parece um pouco cedo para adicionar isso, não sei de nada importante que possa mudar isso antes que o C ++ 2x seja feito.
Shafik Yaghmour

@ ShafikYaghmour Sim, provavelmente MUITO cedo demais. Mas, como o SD era o prazo final para novos recursos de idioma, esses são os dois únicos em andamento que eu conheço - na pior das hipóteses, excluo uma dessas seções mais tarde. Acabei de ver a pergunta ativa com a recompensa e achei que era um bom momento para conversar antes que eu esqueça.
Barry

Entendo, fui tentado algumas vezes em casos semelhantes. Sempre me preocupo que algo importante mude e acabarei tendo que reescrevê-lo.
Shafik Yaghmour

@ ShafikYaghmour Parece que nada vai mudar aqui :)
Barry

Espero que isso seja atualizado agora, com o C ++ 20 já lançado
Noone AtAll
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.