Czy w tym konkretnym przypadku istnieje różnica między używaniem listy inicjatorów elementu członkowskiego a przypisywaniem wartości w konstruktorze?


90

Czy wewnątrz i na temat wygenerowanego kodu istnieje różnica między:

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

i

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

dzięki...

Odpowiedzi:


61

Zakładając, że te wartości są typami pierwotnymi, to nie, nie ma różnicy. Listy inicjalizacyjne mają znaczenie tylko wtedy, gdy masz obiekty jako elementy członkowskie, ponieważ zamiast używać domyślnej inicjalizacji, po której następuje przypisanie, lista inicjalizacyjna umożliwia zainicjowanie obiektu do jego ostatecznej wartości. W rzeczywistości może to być zauważalnie szybsze.


17
jak powiedział Richard, to robi różnicę, jeśli te wartości są typami prymitywnymi i stałymi, listy inicjalizacyjne są jedynym sposobem przypisania wartości do elementów stałych.
thbusch

11
Działa tak, jak opisano, gdy zmienne nie są referencjami ani stałymi, jeśli są stałe lub referencyjne, nie zostanie nawet skompilowany bez użycia listy inicjalizacyjnej.
stefanB

77

Musisz użyć listy inicjalizacyjnej, aby zainicjować stałe składowe, odwołania i klasę bazową

Kiedy musisz zainicjować stały element członkowski, odwołania i przekazać parametry do konstruktorów klasy bazowej, jak wspomniano w komentarzach, musisz użyć listy inicjalizacyjnej.

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

Przykładowa (niewyczerpująca) klasa / struktura zawiera odniesienie:

struct bb {};

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

// usage:

bb b;
aa a(b);

I przykład inicjalizacji klasy bazowej, która wymaga parametru (np. Brak domyślnego konstruktora):

struct bb {};

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

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};

4
A jeśli _capacity, _datai _lenmają typy klas bez dostępnych domyślnych konstruktorów?
CB Bailey,

2
Wywołujesz dostępne konstruktory, jeśli potrzebujesz ustawić więcej wartości, wywołujesz je z treści konstruktora. Różnica polega na tym, że nie możesz zainicjować constelementu członkowskiego w treści konstruktora, musisz użyć listy inicjalizacyjnej - osoby niebędące constskładnikami mogą zostać zainicjowane w liście inicjalizacyjnej lub w treści konstruktora.
stefanB

@stefan: Nie możesz wywoływać konstruktorów. Klasa bez domyślnego konstruktora musi zostać zainicjowana na liście inicjatorów, podobnie jak członkowie const.
GManNickG,

2
musisz także zainicjować odniesienia na liście inicjalizacyjnej
Andriy Tylychko

2
@stefanB: Przepraszam, jeśli zasugerowałem, że nie rozumiesz tego, kiedy faktycznie to rozumiesz. W swojej odpowiedzi podałeś tylko, kiedy baza lub element członkowski musi być nazwany na liście inicjalizatorów, nie wyjaśniłeś, jaka jest koncepcyjna różnica między inicjalizacją w liście inicjalizującej a przypisywaniem w treści konstruktora, w rzeczywistości jest to, gdzie moje nieporozumienie może pochodzić.
CB Bailey,

18

Tak. W pierwszym przypadku można zadeklarować _capacity, _dataa _lenjako stałe:

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

Byłoby to ważne, jeśli chcesz zapewnić const-ness tych zmiennych instancji podczas obliczania ich wartości w czasie wykonywania, na przykład:

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

constwystąpienia muszą być zainicjowane na liście inicjatorów lub typy bazowe muszą zapewniać publiczne konstruktory bez parametrów (które robią typy pierwotne).


2
To samo dotyczy referencji. Jeśli twoja klasa ma członków referencyjnych, muszą zostać zainicjowane na liście inicjatorów.
Mark Ransom

7

Myślę, że ten link http://www.cplusplus.com/forum/articles/17820/ daje doskonałe wyjaśnienie - szczególnie dla tych, którzy są nowicjuszami w C ++.

Powodem, dla którego listy inicjalizujące są bardziej wydajne, jest to, że w treści konstruktora odbywają się tylko przypisania, a nie inicjalizacja. Więc jeśli masz do czynienia z typem niewbudowanym, domyślny konstruktor dla tego obiektu został już wywołany przed wprowadzeniem treści konstruktora. W treści konstruktora przypisujesz wartość do tego obiektu.

W efekcie jest to wywołanie domyślnego konstruktora, po którym następuje wywołanie operatora przypisania kopiowania. Lista inicjalizatorów umożliwia bezpośrednie wywołanie konstruktora kopiującego, co czasami może być znacznie szybsze (pamiętaj, że lista inicjalizacyjna znajduje się przed ciałem konstruktora)


5

Dodam, że jeśli masz elementy typu klasy bez domyślnego konstruktora, inicjalizacja jest jedynym sposobem na zbudowanie Twojej klasy.


3

Duża różnica polega na tym, że przypisanie może zainicjować członków klasy nadrzędnej; inicjator działa tylko na elementach zadeklarowanych w bieżącym zakresie klasy.


2

Zależy od zaangażowanych typów. Różnica jest podobna między

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

i

std::string a("hai");

gdzie druga forma to lista inicjalizacyjna - to znaczy, robi różnicę, czy typ wymaga argumentów konstruktora, czy jest bardziej wydajny z argumentami konstruktora.


1

Prawdziwa różnica sprowadza się do tego, jak kompilator gcc generuje kod maszynowy i rozkłada pamięć. Wyjaśnić:

  • (phase1) Przed treścią init (łącznie z listą init): kompilator przydziela wymaganą pamięć dla klasy. Klasa już żyje!
  • (phase2) W treści inicjalizacyjnej: ponieważ pamięć jest przydzielona, ​​każde przypisanie wskazuje teraz operację na już istniejącej / 'zainicjowanej' zmiennej.

Z pewnością istnieją inne sposoby obsługi składowych typu const. Aby jednak ułatwić sobie życie, twórcy kompilatorów gcc decydują się na ustanowienie pewnych reguł

  1. Elementy członkowskie typu const muszą zostać zainicjowane przed treścią inicjalizacji.
  2. Po fazie 1 każda operacja zapisu jest ważna tylko dla niestałych elementów członkowskich.

1

Istnieje tylko jeden sposób inicjowania wystąpień klas bazowych i niestatycznych zmiennych składowych, a jest to użycie listy inicjalizatora.

Jeśli nie określisz podstawowej lub niestatycznej zmiennej składowej na liście inicjalizatora twojego konstruktora, to ten element członkowski lub baza zostanie zainicjowana domyślnie (jeśli element członkowski / baza jest typem klasy innym niż POD lub tablicą klasy innej niż POD typy) lub pozostawione niezainicjowane w inny sposób.

Po wprowadzeniu treści konstruktora wszystkie bazy lub składowe zostaną zainicjowane lub pozostawione niezainicjowane (tj. Będą miały nieokreśloną wartość). W treści konstruktora nie ma możliwości wpływania na sposób ich inicjalizacji.

Możesz mieć możliwość przypisania nowych wartości do elementów członkowskich w treści konstruktora, ale nie jest możliwe przypisanie do constelementów członkowskich lub elementów członkowskich typu klasy, których nie można przypisać, i nie jest możliwe ponowne powiązanie odwołań.

W przypadku typów wbudowanych i niektórych typów zdefiniowanych przez użytkownika przypisanie w treści konstruktora może mieć dokładnie taki sam efekt, jak inicjowanie z tą samą wartością na liście inicjatorów.

Jeśli nie możesz nazwać członka lub bazy na liście inicjalizatorów, a ta jednostka jest referencją, ma typ klasy bez dostępnego konstruktora domyślnego zadeklarowanego przez użytkownika, jest constkwalifikowana i ma typ POD lub jest typem klasy POD lub tablicą typu klasy POD zawierającego constkwalifikowanego członka (bezpośrednio lub pośrednio), program jest źle sformułowany.


0

Jeśli napiszesz listę inicjalizującą, zrobisz wszystko w jednym kroku; jeśli nie napiszesz listy inicjalizatora, wykonasz 2 kroki: jeden do deklaracji i jeden do przypisania wartości.


0

Istnieje różnica między listą inicjalizacyjną a instrukcją inicjalizacyjną w konstruktorze. Rozważmy poniższy kod:

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

Gdy używana jest MyClass, wszyscy członkowie zostaną zainicjowani przed pierwszą instrukcją w wykonywanym konstruktorze.

Ale gdy jest używana MyClass2, wszyscy członkowie nie są inicjowani, gdy wykonywana jest pierwsza instrukcja w konstruktorze.

W późniejszym przypadku może wystąpić problem z regresją, gdy ktoś dodał kod w konstruktorze przed zainicjowaniem określonego elementu członkowskiego.


0

Oto punkt, o którym nie widziałem, aby inni się do niego odnosili:

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

Klasa tymczasowa nie ma domyślnego ctora. Kiedy używamy go w innej klasie w następujący sposób:

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

kod się nie skompiluje, ponieważ kompilator nie wie jak zainicjalizować obj, ponieważ ma tylko jawny ctor, który otrzymuje wartość int, więc musimy zmienić ctor w następujący sposób:

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

Więc nie chodzi tylko o const i referencje!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.