O que é essa estranha syntax de membro de dois-pontos (“:”) no construtor?

Recentemente eu vi um exemplo como o seguinte:

#include  class Foo { public: int bar; Foo(int num): bar(num) {}; }; int main(void) { std::cout << Foo(42).bar << std::endl; return 0; } 

O que isso estranho : bar(num) significa? De alguma forma parece inicializar a variável membro, mas eu nunca vi essa syntax antes. Parece uma chamada de function / construtor, mas para um int ? Não faz sentido para mim. Talvez alguém possa me esclarecer. E, a propósito, existem outras características de linguagem esotérica como essa, você nunca encontrará em um livro comum de C ++?

É uma lista de boot de membros . Você deve encontrar informações sobre isso em qualquer bom livro C ++ .

Você deve, na maioria dos casos, inicializar todos os objects membros na lista de boot do membro (no entanto, observe as exceções listadas no final da input da FAQ).

O ponto de takeaway da input da FAQ é que,

Todas as outras coisas sendo iguais, seu código será executado mais rapidamente se você usar listas de boot em vez de atribuição.

 Foo(int num): bar(num) 

Essa construção é chamada de Lista de Inicializadores de Membro em C ++.

Simplesmente dito, ele inicializa sua bar membros para um valor num .


Qual é a diferença entre boot e atribuição dentro de um construtor?

Inicialização de membros:

 Foo(int num): bar(num) {}; 

Atribuição de membro:

 Foo(int num) { bar = num; } 

Há uma diferença significativa entre inicializar um membro usando a lista de inicializadores de membros e atribuí-lo um valor dentro do corpo do construtor.

Quando você inicializa campos através da lista inicializadora de membros, os construtores serão chamados uma vez e o object será construído e inicializado em uma operação.

Se você usar a atribuição , os campos serão inicializados primeiro com os construtores padrão e, em seguida, reatribuídos (por meio do operador de atribuição) com os valores reais.

Como você vê, há uma sobrecarga adicional de criação e atribuição no último, o que pode ser considerável para classs definidas pelo usuário.

 Cost of Member Initialization = Object Construction Cost of Member Assignment = Object Construction + Assignment 

Este último é realmente equivalente a:

 Foo(int num) : bar() {bar = num;} 

Enquanto o primeiro é equivalente a apenas:

 Foo(int num): bar(num){} 

Para um membro inerente (seu exemplo de código) ou de class POD, não há sobrecarga prática.


Quando você tem que usar lista inicializador de membros?

Você terá (em vez disso forçado) usar uma lista de Inicializador de Membro se:

  • Sua turma tem um membro de referência
  • Sua class tem um membro const não estático ou
  • Seu membro da class não tem um construtor padrão ou
  • Para boot de membros da class base ou
  • Quando o nome do parâmetro do construtor é o mesmo que o membro de dados (isso não é realmente uma obrigação)

Um exemplo de código:

 class MyClass { public: //Reference member, has to be Initialized in Member Initializer List int &i; int b; //Non static const member, must be Initialized in Member Initializer List const int k; //Constructor's parameter name b is same as class data member //Other way is to use this->b to refer to data member MyClass(int a, int b, int c):i(a),b(b),k(c) { //Without Member Initializer //this->b = b; } }; class MyClass2:public MyClass { public: int p; int q; MyClass2(int x,int y,int z,int l,int m):MyClass(x,y,z),p(l),q(m) { } }; int main() { int x = 10; int y = 20; int z = 30; MyClass obj(x,y,z); int l = 40; int m = 50; MyClass2 obj2(x,y,z,l,m); return 0; } 
  • MyClass2 não tem um construtor padrão, portanto, ele deve ser inicializado por meio da lista de inicializadores de membros.
  • A class base MyClass não possui um construtor padrão. Portanto, para inicializar seu membro, será necessário usar a Lista de Inicializadores de Membro.

Versão online do exemplo de código .


Pontos importantes para Observação ao usar as listas de inicializadores de membros:

As variables ​​de membro de class são sempre inicializadas na ordem em que são declaradas na class.

Eles não são inicializados na ordem em que são especificados na Lista de iniciadores do membro.
Em resumo, a lista de boot de membros não determina a ordem de boot.

Dado o acima, é sempre uma boa prática manter a mesma ordem de membros para boot de Membro como a ordem em que eles são declarados na definição de class. Isso ocorre porque os compiladores não avisam se os dois pedidos são diferentes, mas um usuário relativamente novo pode confundir a lista inicializador de membros como a ordem de boot e escrever algum código dependente disso.

Isso é boot de construtor. É a maneira correta de inicializar membros em um construtor de class, já que isso evita que o construtor padrão seja chamado.

Considere estes dois exemplos:

 // Example 1 Foo(Bar b) { bar = b; } // Example 2 Foo(Bar b) : bar(b) { } 

No exemplo 1:

 Bar bar(); // default constructor bar = b; // assignment 

No exemplo 2:

 Bar bar(b) // copy constructor 

É tudo sobre eficiência.

Isso é chamado de lista de boot. É uma maneira alternativa de inicializar os alunos. Há benefícios em usar isso em vez de simplesmente atribuir novos valores aos membros no corpo do construtor, mas se você tiver membros de class que sejam constantes ou referências, eles devem ser inicializados.

Isso não é obscuro, é a syntax da lista de boot do C ++

Basicamente, no seu caso, x será inicializado com _x , y com _y , z com _z .

O outro já explicou a você que a syntax que você observa é chamada de “lista de inicializadores de construtor”. Essa syntax permite inicializar os subobjects de base e os subobjects de membro da class de forma personalizada (em vez de permitir que eles inicializem por padrão ou permaneçam não inicializados).

Eu só quero notar que a syntax que, como você disse, “parece uma chamada de construtor”, não é necessariamente uma chamada de construtor. Na linguagem C ++, a syntax () é apenas uma forma padrão de syntax de boot . É interpretado de maneira diferente para tipos diferentes. Para tipos de classs com construtor definido pelo usuário, isso significa uma coisa (na verdade é uma chamada de construtor), para tipos de class sem construtor definido pelo usuário significa outra coisa (chamada de boot de valor ) para empty () ) e para non-class types novamente significa algo diferente (já que os tipos não-class não possuem construtores).

No seu caso, o membro de dados tem o tipo int . int não é um tipo de class, então não tem construtor. Para o tipo int esta syntax significa simplesmente “inicializar bar com o valor de num ” e é isso. É feito exatamente assim, diretamente, nenhum construtor envolvido, já que, mais uma vez, int não é um tipo de class, portanto, não pode ter nenhum construtor.

Eu não sei como você pode sentir falta disso, é bem básico. Essa é a syntax para inicializar variables ​​de membros ou construtores de class base. Ele funciona para tipos de dados simples e antigos, bem como para objects de class.

Esta é uma lista de boot. Ele inicializará os membros antes que o corpo do construtor seja executado. Considerar

 class Foo { public: string str; Foo(string &p) { str = p; }; }; 

vs

 class Foo { public: string str; Foo(string &p): str(p) {}; }; 

No primeiro exemplo, o str será inicializado pelo seu construtor sem argumentos

 string(); 

antes do corpo do construtor Foo. Dentro do construtor foo, o

 string& operator=( const string& s ); 

será chamado em ‘str’ como você faz str = p;

Como no segundo exemplo, str será inicializado diretamente chamando seu construtor

 string( const string& s ); 

com ‘p’ como argumento.

Você está correto, esta é realmente uma maneira de inicializar as variables ​​de membro. Não tenho certeza se há muito benefício nisso, além de expressar claramente que é uma boot. Ter uma “barra = num” dentro do código pode ser movida, excluída ou mal interpretada com muito mais facilidade.

existe outro ‘benefício’

se o tipo de variável de membro não suportar boot nula ou se for uma referência (que não pode ser inicializada nula), então você não tem escolha a não ser fornecer uma lista de boot

É uma lista de boot para o construtor. Em vez de construir x , y padrão e, em seguida, atribuir-lhes os valores recebidos nos parâmetros, esses membros serão inicializados com esses valores imediatamente. Isso pode não parecer muito útil para float s, mas pode economizar bastante tempo com classs personalizadas que são caras de serem construídas.

Não mencionado ainda neste encadeamento: desde o C ++ 11, a lista de inicializadores de membros pode usar a boot de lista (também conhecida como “boot uniforme”, “boot com bracing”):

 Foo(int num): bar{num} {} 

que tem a mesma semântica de boot de lista em outros contextos.