C ++ 11 permite a inicialização em classe de membros não estáticos e não const. O que mudou?

87

Antes do C ++ 11, só podíamos realizar a inicialização em classe em membros const estáticos do tipo integral ou enumeração. Stroustrup discute isso em seu C ++ FAQ , dando o seguinte exemplo:

class Y {
  const int c3 = 7;           // error: not static
  static int c4 = 7;          // error: not const
  static const float c5 = 7;  // error: not integral
};

E o seguinte raciocínio:

Então, por que essas restrições inconvenientes existem? Uma classe é normalmente declarada em um arquivo de cabeçalho e um arquivo de cabeçalho é normalmente incluído em muitas unidades de tradução. No entanto, para evitar regras complicadas de vinculador, C ++ exige que cada objeto tenha uma definição exclusiva. Essa regra seria quebrada se o C ++ permitisse a definição em classe de entidades que precisavam ser armazenadas na memória como objetos.

No entanto, o C ++ 11 relaxa essas restrições, permitindo a inicialização em classe de membros não estáticos (§12.6.2 / 8):

Em um construtor não delegante, se um determinado membro de dados não estáticos ou classe base não for designado por um mem-initializer-id (incluindo o caso em que não há mem-initializer-list porque o construtor não tem ctor-initializer ) e a entidade não é uma classe base virtual de uma classe abstrata (10.4), então

  • se a entidade for um membro de dados não estático que possui um inicializador de chave ou igual , a entidade será inicializada conforme especificado em 8.5;
  • caso contrário, se a entidade for um membro variante (9.5), nenhuma inicialização é realizada;
  • caso contrário, a entidade é inicializada por padrão (8.5).

A seção 9.4.2 também permite a inicialização em classe de membros estáticos não constantes se eles estiverem marcados com o constexprespecificador.

Então, o que aconteceu com as razões para as restrições que tínhamos em C ++ 03? Simplesmente aceitamos as "regras complicadas do vinculador" ou alguma outra coisa mudou que torna isso mais fácil de implementar?

Joseph Mansfield
fonte
5
Nada aconteceu. Os compiladores ficaram mais inteligentes com todos esses modelos apenas de cabeçalho, de forma que agora é uma extensão relativamente fácil.
Öö Tiib
Curiosamente no meu IDE, quando eu seleciono a compilação anterior ao C ++ 11, tenho permissão para inicializar membros integrais const não estáticos
Dean P

Respostas:

67

A resposta curta é que eles mantiveram o vinculador quase o mesmo, à custa de tornar o compilador ainda mais complicado do que antes.

Ou seja, em vez de resultar em várias definições para o vinculador classificar, ainda resulta apenas em uma definição, e o compilador tem que classificá-la.

Também leva a regras um pouco mais complexas para o programador manter ordenadas também, mas é mais simples o suficiente para não ser um grande problema. As regras extras surgem quando você tem dois inicializadores diferentes especificados para um único membro:

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}
};

Agora, as regras extras neste ponto lidam com qual valor é usado para inicializar aquando você usa o construtor não padrão. A resposta para isso é bastante simples: se você usar um construtor que não especifica nenhum outro valor, o 1234seria usado para inicializar a- mas se você usar um construtor que especifica algum outro valor, o 1234é basicamente ignorado.

Por exemplo:

#include <iostream>

class X { 
    int a = 1234;
public:
    X() = default;
    X(int z) : a(z) {}

    friend std::ostream &operator<<(std::ostream &os, X const &x) { 
        return os << x.a;
    }
};

int main() { 
    X x;
    X y{5678};

    std::cout << x << "\n" << y;
    return 0;
}

Resultado:

1234
5678
Jerry Coffin
fonte
1
Parece que isso era bem possível antes. Isso apenas dificultou o trabalho de escrever um compilador. Essa é uma declaração justa?
allyourcode
10
@allyourcode: Sim e não. Sim, tornou a escrita do compilador mais difícil. Mas não, porque também tornou a escrita da especificação C ++ um pouco mais difícil.
Jerry Coffin
Existe uma diferença em como inicializar o membro da classe: int x = 7; ou int x {7} ;?
mbaros
9

Acho que esse raciocínio pode ter sido escrito antes que os modelos fossem finalizados. Afinal, as "regras complicadas de vinculador" necessárias para inicializadores em classe de membros estáticos eram / já eram necessárias para o C ++ 11 oferecer suporte a membros estáticos de modelos.

Considerar

struct A { static int s = ::ComputeSomething(); }; // NOTE: This isn't even allowed,
                                                   // thanks @Kapil for pointing that out

// vs.

template <class T>
struct B { static int s; }

template <class T>
int B<T>::s = ::ComputeSomething();

// or

template <class T>
void Foo()
{
    static int s = ::ComputeSomething();
    s++;
    std::cout << s << "\n";
}

O problema para o compilador é o mesmo em todos os três casos: em qual unidade de tradução ele deve emitir a definição se o código necessário para inicializá-la? A solução simples é emiti-lo em todos os lugares e deixar o linker resolvê-lo. É por isso que os vinculadores já suportam coisas como __declspec(selectany). Simplesmente não teria sido possível implementar C ++ 03 sem ele. E é por isso que não foi necessário estender o vinculador.

Para ser mais direto: acho que o raciocínio dado no padrão antigo está simplesmente errado.


ATUALIZAR

Como Kapil apontou, meu primeiro exemplo nem mesmo é permitido no padrão atual (C ++ 14). Deixei de qualquer maneira, porque IMO é o caso mais difícil para a implementação (compilador, vinculador). Meu ponto é: mesmo nesse caso, não é mais difícil do que o que já é permitido, por exemplo, ao usar modelos.

Paul Groke
fonte
É uma pena que isso não tenha obtido votos positivos, pois muitos dos recursos do C ++ 11 são semelhantes no sentido de que os compiladores já incluem os recursos ou otimizações necessárias.
Alex Court
@AlexCourt Escrevi esta resposta recentemente. A pergunta e a resposta de Jerry são de 2012, no entanto. Acho que é por isso que minha resposta não recebeu muita atenção.
Paul Groke
1
Isso não será compatível com "struct A {static int s = :: ComputeSomething ();}" porque apenas const estático pode ser inicializado na classe
PapaDiHatti
8

Em teoria, a So why do these inconvenient restrictions exist?...razão é válida, mas pode ser facilmente contornada e é exatamente isso que o C ++ 11 faz.

Quando você inclui um arquivo, ele simplesmente inclui o arquivo e desconsidera qualquer inicialização. Os membros são inicializados apenas quando você instancia a classe.

Em outras palavras, a inicialização ainda está vinculada ao construtor, apenas a notação é diferente e é mais conveniente. Se o construtor não for chamado, os valores não serão inicializados.

Se o construtor for chamado, os valores serão inicializados com a inicialização na classe, se houver, ou o construtor poderá substituir isso pela própria inicialização. O caminho de inicialização é essencialmente o mesmo, ou seja, por meio do construtor.

Isso é evidente no próprio FAQ do Stroustrup sobre C ++ 11.

zar
fonte
Re "Se o construtor não for chamado, os valores não serão inicializados": Como posso contornar a inicialização do membro da Y::c3pergunta? Pelo que entendi, c3sempre será inicializado, a menos que haja um construtor que substitua o padrão fornecido na declaração.
Peter - Reintegrar Monica em