Nesse caso específico, há uma diferença entre usar uma lista de inicializadores de membros e atribuir valores em um construtor?

90

Internamente e sobre o código gerado, existe realmente uma diferença entre:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

e

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

obrigado...

Stef
fonte

Respostas:

60

Supondo que esses valores sejam tipos primitivos, então não, não há diferença. As listas de inicialização só fazem diferença quando você tem objetos como membros, pois em vez de usar a inicialização padrão seguida pela atribuição, a lista de inicialização permite inicializar o objeto com seu valor final. Na verdade, isso pode ser visivelmente mais rápido.

templatetypedef
fonte
17
como disse Richard, faz diferença se esses valores são tipos primitivos e const, as listas de inicialização são a única maneira de atribuir valores a membros const.
thbusch
11
Só funciona conforme descrito quando as variáveis ​​não são referências ou constantes, se forem constantes ou de referência, nem mesmo será compilado sem usar a lista de inicialização.
stefanB
77

Você precisa usar a lista de inicialização para inicializar membros constantes, referências e classe base

Quando você precisa inicializar um membro constante, referências e passar parâmetros para os construtores da classe base, conforme mencionado nos comentários, você precisa usar a lista de inicialização.

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

Exemplo de classe / estrutura (não exaustiva) contém referência:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

E um exemplo de inicialização de classe base que requer um parâmetro (por exemplo, nenhum construtor padrão):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};
StefanB
fonte
4
E se _capacity, _datae _lentiver tipos de classe sem construtores padrão acessíveis?
CB Bailey
2
Você chama quais construtores estão disponíveis, se precisar definir mais valores, você os chama do corpo do seu construtor. A diferença aqui é que você não pode inicializar o constmembro no corpo do construtor, você deve usar a lista de inicialização - não constmembros podem ser inicializados na lista de inicialização ou no corpo do construtor.
stefanB
@stefan: Você não pode chamar construtores. Uma classe sem construtor padrão deve ser inicializada na lista de inicializadores, assim como os membros const.
GManNickG
2
você também deve inicializar as referências na lista de inicialização
Andriy Tylychko
2
@stefanB: Minhas desculpas se eu sugeri que você não entende isso quando na verdade entende. Em sua resposta, você apenas declarou quando uma base ou membro deve ser nomeado na lista de inicializadores, você não explicou qual é a diferença conceitual entre inicializar na lista de inicializadores e atribuir no corpo do construtor, que é onde meu mal-entendido pode ter vindo.
CB Bailey
18

Sim. No primeiro caso, você pode declarar _capacity, _datae _lencomo constantes:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Isso seria importante se você quiser garantir a const-ness dessas variáveis ​​de instância ao calcular seus valores em tempo de execução, por exemplo:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constas instâncias devem ser inicializadas na lista de inicializadores ou os tipos subjacentes devem fornecer construtores públicos sem parâmetros (o que os tipos primitivos fornecem).

Richard Cook
fonte
2
O mesmo vale para referências. Se sua classe possui membros de referência, eles devem ser inicializados na lista de inicializadores.
Mark Ransom
7

Acho que este link http://www.cplusplus.com/forum/articles/17820/ dá uma explicação excelente - especialmente para aqueles que são novos em C ++.

A razão pela qual as listas de inicializadores são mais eficientes é que, dentro do corpo do construtor, apenas as atribuições ocorrem, não a inicialização. Portanto, se você estiver lidando com um tipo não interno, o construtor padrão para esse objeto já foi chamado antes que o corpo do construtor tenha sido inserido. Dentro do corpo do construtor, você está atribuindo um valor a esse objeto.

Na verdade, esta é uma chamada para o construtor padrão seguida por uma chamada para o operador de atribuição de cópia. A lista de inicializadores permite que você chame o construtor de cópia diretamente, e isso às vezes pode ser significativamente mais rápido (lembre-se de que a lista de inicializadores está antes do corpo do construtor)

user929404
fonte
5

Acrescentarei que, se você tiver membros do tipo de classe sem nenhum construtor padrão disponível, a inicialização é a única maneira de construir sua classe.

Fred Larson
fonte
3

Uma grande diferença é que a atribuição pode inicializar membros de uma classe pai; o inicializador funciona apenas em membros declarados no escopo da classe atual.

Mark Ransom
fonte
2

Depende dos tipos envolvidos. A diferença é semelhante entre

std::string a;
a = "hai";

e

std::string a("hai");

onde a segunda forma é a lista de inicialização - isto é, faz diferença se o tipo requer argumentos do construtor ou é mais eficiente com argumentos do construtor.

Cachorro
fonte
1

A verdadeira diferença se resume em como o compilador gcc gera código de máquina e armazena a memória. Explicar:

  • (fase1) Antes do corpo de inicialização (incluindo a lista de inicialização): o compilador aloca a memória necessária para a classe. A classe já está viva!
  • (fase2) No corpo de inicialização: como a memória está alocada, cada atribuição agora indica uma operação na variável já existente / 'inicializada'.

Certamente, existem outras maneiras de lidar com membros do tipo const. Mas para facilitar sua vida, os escritores do compilador gcc decidem definir algumas regras

  1. Os membros do tipo const devem ser inicializados antes do corpo init.
  2. Após a fase 1, qualquer operação de gravação é válida apenas para membros não constantes.
lukmac
fonte
1

Existe apenas uma maneira de inicializar instâncias de classe base e variáveis ​​de membro não estáticas e é usando a lista de inicializadores.

Se você não especificar uma variável de membro base ou não estática na lista de inicializadores de seu construtor, então esse membro ou base será inicializado por padrão (se o membro / base for um tipo de classe não POD ou array de classe não POD tipos) ou deixados não inicializados.

Uma vez que o corpo do construtor é inserido, todas as bases ou membros terão sido inicializados ou deixados não inicializados (ou seja, eles terão um valor indeterminado). Não há oportunidade no corpo do construtor de influenciar como eles devem ser inicializados.

Você pode atribuir novos valores a membros no corpo do construtor, mas não é possível atribuir a constmembros ou membros do tipo de classe que se tornaram não atribuíveis e não é possível religar referências.

Para tipos integrados e alguns tipos definidos pelo usuário, a atribuição no corpo do construtor pode ter exatamente o mesmo efeito que inicializar com o mesmo valor na lista de inicializadores.

Se você não conseguir nomear um membro ou base em uma lista de inicializadores e essa entidade for uma referência, tiver tipo de classe sem nenhum construtor padrão declarado pelo usuário acessível, for constqualificado e ter tipo POD ou for um tipo de classe POD ou array do tipo de classe POD contendo um constmembro qualificado (direta ou indiretamente), então o programa está mal formado.

CB Bailey
fonte
0

Se você escrever uma lista de inicializadores, fará tudo em uma única etapa; se você não escrever uma lista do initilizer, você fará 2 passos: um para declaração e outro para atribuir o valor.

L. Vicente Mangas
fonte
0

Há uma diferença entre a lista de inicialização e a instrução de inicialização em um construtor. Vamos considerar o código abaixo:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

Quando MyClass é usado, todos os membros serão inicializados antes da primeira instrução em um construtor executado.

Mas, quando MyClass2 é usado, todos os membros não são inicializados quando a primeira instrução em um construtor é executada.

No caso posterior, pode haver problema de regressão quando alguém adiciona algum código em um construtor antes de um determinado membro ser inicializado.

user928143
fonte
0

Aqui está um ponto que não vi outros se referirem a ele:

class temp{
public:
   temp(int var);
};

A classe temporária não possui um ctor padrão. Quando o usamos em outra classe, como segue:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

o código não vai compilar, porque o compilador não sabe como inicializar obj, porque ele tem apenas um ctor explícito que recebe um valor int, então temos que mudar o ctor da seguinte maneira:

mainClass(int sth):obj(sth){}

Portanto, não se trata apenas de const e referências!

hosh0425
fonte