2011-08-26 12 views
6

Jestem nowicjuszem w C++, a cała idea zajęć - wciąż czytam książkę, aby spróbować i się uczyć. Książka Czytam mówi, że kiedy skonstruować klasy, mogę przypisać wartości domyślne w ten sposób:Konstrukcja klasy z początkowymi wartościami

class foo { 
public: 
    foo(char c, int i); 
private: 
    char exampleChar; 
    int exampleInt; 
}; 

foo::foo(char c, int i): 
exampleChar(c), 
exampleInt(i) 
{} 

ten kod (do mnie) wygląda bardzo brudny i nie przestrzegać zasad, że jestem używane w innych językach. Moje pytanie brzmi, jaka jest różnica między powyższym a tym (poniżej, które osobiście uważam za dużo czystsze)?

foo::foo(char c, int i) { 
    exampleChar = c; 
    exampleInt = i; 
} 

Sortuj rzeczy myślę o to: czy są kwestie wydajności/efektywności jeżeli odbywa się na dużą skalę - czy jest to dokładnie to samo?

+1

"Ten kod ... nie przestrzega reguł, do których jestem przyzwyczajony w innych językach" Cóż, C++ nie jest jednym z tych języków. Jest wiele rzeczy w C++, które różnią się od "innych języków" (i, prawdopodobnie, jest wiele rzeczy w każdym z tych innych języków, które różnią się od innych z tych języków). –

+1

bardzo podobne pytania: http://stackoverflow.com/questions/1598967/benefits-of-initialization-lists, http://stackoverflow.com/questions/926752/why-should-i-prefer-to-use-member -initialization-list, http://stackoverflow.com/questions/4589237/c-initialization-lists, i prawdopodobnie znacznie więcej –

Odpowiedz

10

Pierwszy sposób, wykonując : exampleChar(c), exampleInt(i) nazywany jest listą inicjalizatora.

jeśli robisz to drugi sposób, że dwie zmienne są domyślne zbudowane pierwszy , następnie przypisać im wartość. (Po wprowadzeniu faktycznej treści konstruktora, wszystko, co nie zostało zainicjowane przez listę inicjalizatorów, jest domyślnie skonstruowane.) Jest to strata czasu, ponieważ i tak po prostu nadpisujesz wartości. W przypadku małych typów, takich jak int lub char nie jest to wielka sprawa, ale gdy te zmienne składowe są dużymi typami, które wymagałyby wielu cykli do skonstruowania, zdecydowanie warto użyć listy inicjalizatora.

Drugi sposób nie zmarnuje czasu, nadając im domyślną wartość, a następnie nadpisując - ustawi ich wartości bezpośrednio na wartość, którą podasz (lub wywoła odpowiedni konstruktor, jeśli element jest obiektem).

Można zobaczyć, co mamy na myśli w ten sposób:

class MyClass { 
public: 
    int _i; // our data 

    // default constructor 
    MyClass() : _i(0) { cout << "default constructor"; } 

    // constructor that takes an int 
    MyClass(int i) : _i(i) { cout << "int constructor"; } 

    // assignment operator 
    void operator=(int i) { _i = i; cout << "assignment operator"; } 
}; 

class OtherClass { 
public: 
    MyClass c; 

    OtherClass() { 
     c = 54; 
    } 
}; 

OtherClass oc; 

Zobaczysz, że

default constructor 
assignment operator 

zostanie wydrukowany. To są dwie funkcje, które w przypadku innych klas mogą być drogie.

Jeżeli zmienisz konstruktora OtherClass do

OtherClass() : c(54) { } 

Zobaczysz, że

int constructor 

zostanie wydrukowany. Tylko jedno połączenie w porównaniu do dwóch. To jest najbardziej efektywny sposób.

listy initializer są również koniecznością kiedy

  1. mieć typy, które nie mają domyślnego konstruktora. Musisz wywołać odpowiedni konstruktor na liście inicjalizatora.

  2. mają const element, który chcesz, aby dać pewną wartość (a nie tylko mieć permantently wartość domyślna

  3. posiada człon odniesienia. Musisz użyć list initializer na nich.

tl; dr: zrób to, ponieważ jest co najmniej tak szybki, ale nigdy wolniejszy niż w inny sposób, a czasami znacznie szybciej.i char, w rzeczywistości nie są one w ogóle konstruowane; mają po prostu wartość jakiejkolwiek pamięci, którą mieli wcześniej.

+0

C++ nie przypisuje wartości domyślnych prymitywom. Dlatego używanie nieprzypisanych zmiennych daje ostrzeżenie - jest to niezdefiniowane zachowanie, ponieważ zawiera losową wartość, która znajduje się w tym bloku pamięci. –

+0

@Yochai dobry połów, zapomniałem o tym wspomnieć. Dodano notatkę. –

1

Cóż, jest to typowy FAQ pytanie: http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.6

W twoim przypadku, używając char i int nie ma różnic.

Zasada ogólna: użyj listy inicjalizacji jak possibile, istnieje bardzo niewiele przypadków, kiedy można wolą zadanie, na przykład w celu zwiększenia czytelności:

MyClass::MyClass 
{ 
    a = b = c = d = e = f = 0; 
} 

jest lepszy niż

class MyClass::MyClass : a(0), b(0), c(0), d(0), e(0), f(0) { } 
5

Różnica polega kompilator zawsze inicjalizuje wszystkich członków (w kolejności deklaracji) przed pierwszą instrukcją konstruktora zdefiniowaną przez użytkownika. W przypadku modelu char i int, które są typami pierwotnymi, "inicjalizacja" oznacza tutaj "brak inicjalizacji". Jednakże, jeśli masz członek z konstruktora, dokłada jakąś faktyczną pracę, to konstruktor zostanie wywołany z góry - jeśli nie

foo::foo() { 
    myComplexMember = MyComplexClass(42); 
} 

kompilator nie już wywołać konstruktora domyślnego MyComplexClass przed kodem mnie nazywa, co jest strata zasobów (i błąd kompilatora, jeśli domyślny ctor nie jest dostępny).

Za pomocą listy inicjalizacji można dostosować domyślną inicjalizację i unikać robienia rzeczy bez niczego. Oczywiście, to jest droga.

5

Są rzeczy, które możesz zrobić tak, że nie można inaczej.

  1. Jeśli jeden z członków nie ma domyślnego konstruktora. To jedyny sposób, żeby zainicjować członka przy budowie. (to samo dotyczy klasy bazowej)

  2. Możesz przypisać wartość członkowi const.

  3. Można zapewnić zdefiniowany stan klasy przed uruchomieniem funkcji konstruktora.

  4. Jeśli element jest odwołaniem, należy go zainicjować na liście inicjowania. Ponieważ referencje są niezmienne i mogą być inicjowane tylko raz na początku (jak const)

1

Jeżeli członkowie mieli nietrywialne konstruktorów w kodzie poniżej pierwszych domyślnych konstruktorów można nazwać, a następnie przypisania byłaby wykonywane w powyższym kodzie, zostaną zainicjowane tylko jeden raz. Tak, może być problem z wydajnością.

Istnieje również problem praktyczny: jeśli są to stałe, odniesienia lub nie mają domyślnych konstruktorów, nie można użyć poniższej wersji.

1

Istnieje subtelna, ale ważna różnica między tymi dwoma opcjami. To, co masz na górze, nazywa się listą inicjalizacji członków. Kiedy obiekt zostanie utworzony, członkowie z tej listy są inicjowani do tego, co umieściliście w nawiasie.

Kiedy robisz zadanie w ciele konstruktora, wartości są najpierw zainicjowany, a następnie przypisany. Opublikuję krótki przykład poniżej.

Przykład 1: inicjalizacja Użytkownik

class foo 
{ 
public: 
    foo(char c, int i); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i): 
    exampleChar(c), 
    exampleInt(i), 
    exampleBar()  //Here, a bar is being default constructed 
{ 
} 

Przykład 2: Konstruktor Przypisanie

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    //exampleBar() 
{ 
    exampleChar = c; 
    exampleInt = i; 
    exampleBar = someOtherBar; //Here, a bar is being assigned 
} 

To gdzie robi się ciekawie. Zauważ, że przypisywane jest exampleBar. Poza kulisami, Bar jest w pierwszej kolejności domyślnie skonstruowany, nawet jeśli tego nie określiłeś. Ponadto, jeśli twoja Bar jest bardziej skomplikowana niż prosta struktura, musisz koniecznie zaimplementować operatora przypisania, abyś mógł go zainicjować w ten sposób. Co więcej, aby zainicjować Bar z innego Bar z listy inicjalizacji członków, musisz zaimplementować konstruktora kopiowania!

Przykład 3: Kopiowanie konstruktor stosowane w państwach init,

class foo 
{ 
public: 
    foo(char c, int i, Bar b); 

private: 
    char exampleChar; 
    int exampleInt; 
    Bar exampleBar; 
}; 

foo::foo(char c, int i, Bar b): 
    //exampleChar(c), 
    //exampleInt(i), 
    exampleBar(b)  //Here, a bar is being constructed using the copy constructor of Bar 
{ 
    exampleChar = c; 
    exampleInt = i; 
} 
0

chciałbym dostać się do nawyku korzystania z list inicjalizacji. Nie będą cierpieć z powodu problemów, gdy ktoś zmieni znak do jakiegoś obiektu, w którym domyślny konstruktor zostanie wywołany jako pierwszy, a także do poprawności stałych dla wartości stałych!

0
foo::foo(char c, int i):exampleChar(c),exampleInt(i){} 

Konstrukt ten nazywany jest Użytkownik Initializer Lista w C++.

To inicjuje swój członek exampleChar do wartości c & exampleInt do i.


Jaka jest różnica między Inicjowanie i przydzielanie wewnątrz konstruktora? &
Co to jest korzyść?

Występuje różnica między Inicjowanie elementu przy użyciu listy inicjalizatora i przypisywanie mu wartości w treści konstruktora.

Po zainicjowaniu pól za pomocą listy inicjalizatora, konstruktorzy zostaną wywołani jeden raz.

Jeśli użyjesz przypisania, pola zostaną najpierw zainicjowane za pomocą domyślnych konstruktorów, a następnie ponownie przydzielone (za pośrednictwem operatora przypisania) z aktualnymi wartościami.

Jak widzisz, istnieje dodatkowe obciążenie związane z tworzeniem &, które może być znaczące dla klas zdefiniowanych przez użytkownika.

Dla typu danych liczbowych całkowitych (dla których go używasz) lub członków klasy POD nie ma praktycznego narzutu.

Powiązane problemy