2009-05-11 39 views
19

Jestem całkiem nowy w C++ i nie jestem tego pewien. Spójrz na poniższy przykład, który podsumowuje mój obecny problem.C++ - budowa obiektu wewnątrz klasy

class Foo 
{ 
    //stuff 
}; 

class Bar 
{ 
    Foo foo; 
}; 

Pasek So tworzy pełen obiekt Foo, a nie tylko referencję lub wskaźnik. Czy ten obiekt został zainicjowany przez jego domyślny konstruktor? Czy muszę jawnie wywoływać jego konstruktora, a jeśli tak, to w jaki sposób i gdzie?

Dzięki.

+4

Jestem zaznajomiony z C++ i ciągle mam pewne wątpliwości w tym zakresie. Co więcej, większość odpowiedzi wyraźnie stwierdza, że ​​domyślny konstruktor Foo zostanie wywołany, a faktem jest, że zależy to od definicji samego Foo. Czy jest to domyślny konstruktor dostarczony przez użytkownika lub domyślny? Czy ma jakieś prywatne atrybuty członka? Inicjalizacja w C++ nie jest prosta. –

+2

Całkiem zabawne, że @xtofl prosi plakat o usunięcie "Jestem zaznajomiony z C++" ... Może większość ludzi nie jest tak "zaznajomiona" z C++, gdy prawie wszystkie odpowiedzi są błędne. W rzeczywistości inicjowanie jest trudne, niektóre osoby, które udzieliły odpowiedzi, udowodniły swoją znajomość C++ @JaredPar, @dirkgently, @David Thornley, a mimo to nie udało się. –

Odpowiedz

17

To będzie inicjowana przez jego domyślny konstruktor. Jeśli chcesz użyć innego konstruktora, może masz coś takiego:

class Foo 
{ 
    public: 
    Foo(int val) { } 
    //stuff 
}; 

class Bar 
{ 
    public: 
    Bar() : foo(2) { } 

    Foo foo; 
}; 
+0

Błąd składniowy: potrzebujesz dwukropka (:) po publicznym słowie kluczowym. – dirkgently

+2

I półkolumny (;) po zamknięciu} każdej klasy. Obwiniam Javę. – richq

+0

dzięki za poprawki do składni. Obwiniam C#. –

1

So Bar constains a full Foo object, not just a reference or pointer. Is this object initialized by its default constructor?

Jeśli Foo ma domyślny konstruktor, obiekt typu Foo będzie używany domyślny konstruktor podczas tworzenia obiektu typu Bar. W przeciwnym razie, musisz zadzwonić pod numer Foo, lub Twój ctor Bar sprawi, że Twój kompilator złoży skargę na nie.

Np

class Foo { 
public: 
Foo(double x) {} 
}; 

class Bar { 
Foo x; 
}; 

int main() { 
Bar b; 
} 

Powyższe będzie miało kompilator skarżą się coś takiego:

"In constructor 'Bar::Bar()': Line 5: error: no matching function for call to 'Foo::Foo()'

Do I need to explicitly call its constructor, and if so, how and where ?

zmodyfikować powyższy przykład następująco:

class Foo { 
public: 
    Foo(double x) {} // non-trivial ctor 
}; 

class Bar {  
Foo x; 
public: 
    Bar() : x(42.0) {} // non-default ctor, so public access specifier required 
}; 

int main() { 
Bar b; 
} 
+0

Jeśli Foo nie ma zdefiniowanego żadnego konstruktora (poza prawdopodobnie konstruktorem kopiującym), kompilator nie będzie narzekał, ale nie wywoła niejawnie zdefiniowanego konstruktora Foo. Obiekt zostanie niezainicjowany. –

+0

To właśnie miałem na myśli domyślnie ctor. – dirkgently

+0

Czy można zainicjować obiekt bez korzystania z list inicjalizacji? W jaki sposób można zainicjować obiekt wewnątrz {} konstruktora? –

2

Jeśli nie jawnie wywołać konstruktora foo wewnątrz konstruktora miejscowości Bar to jeden domyślny będzie używany. Można kontrolować to przez jawne wywołanie konstruktora

Bar::Bar() : foo(42) {} 

Jest to oczywiście przy założeniu, że dodasz Foo :: Foo (int) do kodu :)

+1

Widzę, że większość z was wymyśliła "42" w waszych przykładach. Skąd to pochodzi? – ichiban

+7

Jest to odniesienie do H2G2 - odpowiedzi na ostateczne pytanie o życie, wszechświat i wszystko. Przeczytaj: http://en.wikipedia.org/wiki/42_(number)#In_The_Hitchhiker.27s_Guide_to_the_Galaxy. Jeszcze lepiej przeczytaj Douglasa Adamsa. – dirkgently

+0

@ichiban, @dirkgently jest poprawny. – JaredPar

1

Pełna obiektu. Nie, domyślnie jest skonstruowany w domyślnym konstruktorze Bar.

Teraz, jeśli Foo miał konstruktora, który wziął tylko, powiedzmy, int. należałoby konstruktora w barze, aby wywołać konstruktor Foo, a powiedzieć, co to jest:

class Foo { 
public: 
    Foo(int x) { .... } 
}; 

class Bar { 
public: 
    Bar() : foo(42) {} 

    Foo foo; 
}; 

Ale jeśli Foo miał domyślnego konstruktora Foo(), kompilator generuje konstruktora bar automatycznie, a które nazwałbym domyślne Foo (np. Foo())

+0

Widzę, że większość z was wymyśliła "42" w waszych przykładach. Skąd to pochodzi? – ichiban

+0

Nie działa, wszyscy lekarze są domyślnie prywatni. – dirkgently

+0

Ups. Naprawiony. Staram się to pominąć, gdy myślę/mówię o innych koncepcjach. – Macke

1

O ile nie określisz inaczej, foo jest inicjowane za pomocą domyślnego konstruktora. Jeśli chcesz użyć innego konstruktora, trzeba to zrobić w liście inicjatora na pasku:

Bar::Bar(int baz) : foo(baz) 
{ 
    // Rest of the code for Bar::Bar(int) goes here... 
} 
+0

Jeśli Foo ma zdefiniowany przez użytkownika domyślny konstruktor, zostanie wywołany. Dla niejawnie zdefiniowanego domyślnego konstruktora w Foo, nie zostanie wykonane wywołanie w niejawnie zdefiniowanym konstruktorze paska. –

+0

Czy można zainicjować obiekt bez korzystania z list inicjalizacji? W jaki sposób można zainicjować obiekt wewnątrz {} konstruktora? –

+0

@JustinLiang, nie, po wykonaniu bryły konstruktora wszystkie obiekty należące do klasy, które zostaną zainicjowane, zostały zainicjowane. Jeśli obiekt nie ma domyślnego konstruktora, wymagane jest wywołanie konstruktora na liście inicjalizatora. Można jednak użyć przypisania do zmiany obiektów i danych w klasie, ale należy pamiętać, że jest to mniej efektywne, ponieważ obiekt jest inicjowany, a następnie zastępowany przydziałem. – Naaff

0

Nie trzeba zadzwonić domyślny contructor wyraźnie w C++, zostanie wywołany dla Ciebie. Jeśli chcesz połączyć się z innym contructor, można to zrobić:

Foo foo(somearg) 
+0

Jeśli Foo ma zdefiniowany przez użytkownika domyślny konstruktor, zostanie wywołany. Dla niejawnie zdefiniowanego domyślnego konstruktora w Foo, nie zostanie wykonane wywołanie w niejawnie zdefiniowanym konstruktorze Bar –

4

Istnieją cztery funkcje C++ kompilator wygeneruje dla każdej klasy, jeśli to możliwe, a jeśli nie zapewni im: konstruktor domyślny , konstruktor kopii, operator przypisania i destruktor.W standardzie C++ (rozdział 12, "Funkcje specjalne") są one określane jako "niejawnie zadeklarowane" i "niejawnie zdefiniowane". Będą miały publiczny dostęp.

Nie należy mylić "niejawnie zdefiniowanego" z "domyślnym" w konstruktorze. Domyślnym konstruktorem jest ten, który można wywołać bez żadnych argumentów, jeśli taki istnieje. Jeśli nie podasz żadnego konstruktora, domyślny zostanie domyślnie zdefiniowany. Użyje domyślnych konstruktorów dla każdej klasy bazowej i elementu danych.

Co się dzieje, to że klasa Foo ma domyślnie zdefiniowany domyślny konstruktor, a Bar (który nie ma konstruktora zdefiniowanego przez użytkownika) korzysta z niejawnie zdefiniowanego domyślnego konstruktora, który wywołuje domyślny konstruktor Foo.

Jeśli chcesz napisać konstruktora dla paska, możesz wymienić foo na liście inicjalizacyjnej, ale ponieważ używasz domyślnego konstruktora, nie musisz go określać.

Należy pamiętać, że jeśli napiszesz konstruktor dla Foo, kompilator nie wygeneruje automatycznie domyślnego konstruktora, więc będziesz musiał określić, jeśli go potrzebujesz. Dlatego, jeśli wprowadziłeś coś takiego jak Foo(int n); do definicji Foo i nie jawnie zapisałeś domyślnego konstruktora (albo Foo(); lub Foo(int n = 0);), nie mógłbyś mieć paska w jego obecnej postaci, ponieważ nie mógł on użyć Domyślny konstruktor Foo. W takim przypadku musisz mieć konstruktora takiego jak Bar(int n = 0): foo(n) {} z konstruktorem paska inicjującym Foo. (Zauważ, że Bar(int n = 0) {foo = n;} lub coś podobnego by nie działało, ponieważ konstruktor pręta najpierw spróbuje zainicjować foo, a to by się nie udało.)

+0

Niejawnie zdefiniowany konstruktor w Bar nie wywoła niejawnie zdefiniowanego konstruktora w Foo. Jeśli Foo ma zdefiniowany przez użytkownika konstruktor, wówczas niejawnie zdefiniowany konstruktor baru (tym razem tak) wywoła konstruktor zdefiniowany przez użytkownika w Foo. Jest to najbardziej wyczerpująca odpowiedź, niemniej jednak: +1 –

+0

Jasne o tym? Zgodnie z 12.6.2 (4) niestatyczny element danych jest inicjowany za pomocą domyślnego konstruktora, jeśli nie jest wymieniony na liście inicjalizatora, aw wersji 8.5 (5) wywoływany jest domyślny konstruktor, chyba że jest to POD (zwykłe stare dane, nie dogodnie zdefiniowane w standardzie), w którym to przypadku inicjuje się do zera. Sądzę więc, że pytanie brzmi, czy Foo jest typem POD, którego nie możemy zobaczyć. Jeśli jest to typ POD, to wszystko jest inicjowane zerowo; jeśli ma jedną z rzeczy, które oznaczy ją jako nie-POD, jest inicjowana domyślnie i dlatego wywołuje domyślnie zdefiniowany domyślny konstruktor. –

12

Budowa jest dość trudnym tematem w C++. Prosta odpowiedź to to zależy od. To, czy Foo zostanie zainicjowany, zależy od definicji samego Foo. O drugie pytanie: jak sprawić, aby Bar inicjował Foo: list inicjalizacji są odpowiedzią.

Podczas gdy ogólna zgoda jest taka, że ​​Foo będzie domyślnie inicjowane przez niejawny domyślny konstruktor (wygenerowany przez kompilator), który nie musi mieć wartości true.

Jeśli Foo nie ma zdefiniowanego przez użytkownika konstruktora domyślnego, wówczas Foo nie będzie inicjowane. Aby być bardziej precyzyjnym: każdy członek Bar lub Foo brakuje domyślnego konstruktora zdefiniowany przez użytkownika zostaną niezainicjowanymi przez kompilator generowane domyślnego konstruktora barskiej:

class Foo { 
    int x; 
public: 
    void dump() { std::cout << x << std::endl; } 
    void set() { x = 5; } 
}; 
class Bar { 
    Foo x; 
public: 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
class Bar2 
{ 
    Foo x; 
public: 
    Bar2() : Foo() {} 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
template <typename T> 
void test_internal() { 
    T x; 
    x.dump(); 
    x.set(); 
    x.dump(); 
} 
template <typename T> 
void test() { 
    test_internal<T>(); 
    test_internal<T>(); 
} 
int main() 
{ 
    test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0 
    test<Bar>(); // prints ??, 5, 5, 5 
    test<Bar2>(); // prints 0, 5, 0, 5 
} 

Teraz, jeśli Foo miał konstruktor zdefiniowanej przez użytkownika to byłoby być inicjowane zawsze, niezależnie od tego, czy Bar ma zainicjowany konstruktor, czy nie. Jeśli Bar ma zdefiniowany przez użytkownika konstruktor, który jawnie wywołuje (prawdopodobnie niejawnie zdefiniowany) konstruktor Foo, to w rzeczywistości Foo zostanie zainicjowany. Jeśli lista inicjalizacji paska nie wywoła konstruktora Foo, będzie to odpowiednik przypadku, w którym Bar nie ma konstruktora zdefiniowanego przez użytkownika.

Kod testowy może wymagać wyjaśnienia. Interesuje nas, czy kompilator zainicjalizuje zmienną bez kodu użytkownika, który faktycznie wywołuje konstruktor. Chcemy sprawdzić, czy obiekt jest zainicjowany, czy nie. Teraz, jeśli po prostu utworzymy obiekt w funkcji, może się zdarzyć, że znajdzie się w nienaruszonej pozycji pamięci zawierającej już zera. Chcemy odróżnić szczęście od sukcesu, więc definiujemy zmienną w funkcji i wywołujemy funkcję dwukrotnie. W pierwszym uruchomieniu wydrukuje zawartość pamięci i wymusi zmianę. W drugim wywołaniu funkcji, ponieważ ślad stosu jest taki sam, zmienna będzie utrzymywana w dokładnie tej samej pozycji pamięci. Jeśli został zainicjowany, byłby ustawiony na 0, w przeciwnym razie zachowałby tę samą wartość, co poprzednia zmienna w dokładnie tej samej pozycji.

W każdym z przebiegów testowych pierwsza drukowana wartość jest wartością początkową (jeśli została faktycznie zainicjowana) lub wartością w tej pozycji pamięci, która w niektórych przypadkach wynosi 0. Druga wartość to tylko test token reprezentujący wartość w pozycji pamięci po ręcznej jej zmianie. Trzecia wartość pochodzi z drugiego uruchomienia funkcji. Jeśli zmienna jest inicjalizowana, spadnie z powrotem do 0. Jeśli obiekt nie zostanie zainicjowany, jego pamięć zachowa starą zawartość.

+0

Czy wiesz, dlaczego po uruchomieniu testu () wyjście ma wartość ??, 5, 5, 5 zamiast ??, 5, ??, 5 oznacza, że ​​obiekt Foo (x) nie wykracza poza zasięg, gdy test_internal zwraca ? Podobnie dla testu ()? – user1084113

+0

@ user1084113: Ponieważ jest to niezdefiniowane zachowanie, można uzyskać albo, ale test nadużywa wiedzy o tym, w jaki sposób kompilatory/architektury często używają stosu. Zasadniczo, gdy funkcja zostanie wprowadzona, pobiera część stosu ze swoich zmiennych wewnętrznych, które następnie zwalniają, gdy funkcja się kończy (w zależności od konwencji wywołującej może to być wywołujący lub funkcja aktualizująca wskaźnik stosu). Drugie wywołanie funkcji używa tego samego bloku pamięci dla zmiennych lokalnych, ułożonych w tej samej kolejności. 'X' jest niezainicjalizowane, o wartości przypisanej w ostatnim przebiegu funkcji. –

Powiązane problemy