2015-02-24 19 views
5
#include <iostream> 
struct A 
{ 
    A(){std::cout<<"A()"<<std::endl;} 
}; 

template<typename T> 
struct B 
{ 
    A a; 
    T b; 
    B(){std::cout<<"B()"<<std::endl;} 
}; 

int main() 
{ 
    B<B<B<int> > > Test; 
    return 0; 
} 

odrer wywoływania konstruktorów jestZamówienie wywoływania konstruktorów w jednym przypadku C++

A() 
A() 
A() 
B() 
B() 
B() 

i nie mam pojęcia dlaczego tak jest. Myślałem, że to będzie A B A B A B. Czy możesz mi wyjaśnić, dlaczego?

+3

Zmienne użytkownika są inicjowane zanim skończy ciało konstruktora. – immibis

Odpowiedz

2

To jest rzeczywiście prosta, jeśli to było jak Ababab, wtedy masz problem, jeśli chcesz uzyskać dostęp do b z konstruktora B, ponieważ kolejność myślałeś zakłada, że ​​pierwszy człon a dostaje instancji, a następnie ctor Runs następnie zostanie zainicjowany b. W rzeczywistości, każdy element jest najpierw tworzony (konstruowany itd.), A następnie wywoływani są konstruktorzy.

1

Dzieje się tak, ponieważ zmienne składowe muszą zostać zainicjowane przed wykonaniem treści konstruktora. Rozważmy następujący przykład:

struct A { 
    int value; 

    // Here we explicitly initialize 'value' with 5 
    A() : value(5) { } 
}; 

struct B { 
    A a; 

    B() 
    { 
     // This is perfectly valid and would print 5, 
     // because 'a' has already been implicitly initialized 
     // with its default constructor. 
     std::cout << a.value; 
    } 
}; 

Jeśli tak nie było, jaką wartość można oczekiwać a mieć w konstruktorze B „s? Wystąpiłbyś w różnego rodzaju problemach. Dlatego domyślny konstruktor A musi zostać niejawnie wywołany przed treścią B().

Zasadniczo, aby uczynić go bardziej wyraźne, to jest to, co się dzieje:

// Initialize 'a' before body of constructor 
    B() : a() 
    { 
     std::cout << a.value; 
    } 
1

Przede wszystkim przeanalizujmy, co masz tutaj:

  • masz obiekt Test z class B<B<B<int> > > , który jest:

    class B<B<B<int> > > { 
        A a; 
        B<B<int> > b; 
    }; 
    
  • the seco nd pole Test, Test.b jest class B<B<int> >, który jest:

    class B<B<int> > { 
        A a; 
        B<int> b; 
    }; 
    
  • wtedy masz drugie pole Test.b, Test.b.b, który jest class B<int>, który jest:

    class B<int> { 
        A a; 
        int b; 
    }; 
    

więc kolejność inicjowania to:

  • A() dla Test.a.
  • dla Test.b.a.
  • dla Test.b.b.a.
  • brak konstruktora jako Test.b.b.b jest typu int i nie ma konstruktora.
  • B<int>() dla Test.b.b.
  • dla Test.b.
  • B<B<B<int> > >() dla Test.

Niestety, wszystkie trzy konstruktory wypisują na wyjściu to samo: B(), ale są to różne konstruktory dla różnych klas.

+0

Możliwe jest wyświetlanie zagnieżdżenia: http://ideone.com/45rDMR – stefan

+0

Tak, ale drukujesz w odwrotnej kolejności, najpierw jest drukowane najbardziej zagnieżdżone pole "b". Kolejność może być 'B() 2',' B() 1', 'B() 0'. –

+0

Nie rozumiem, co mówisz. Zlecenie to B (), B >(), ... więc wyświetlane liczby są poprawne: pierwsze wywołanie konstruktora obiektu B do uzupełnienia to B (), stąd B() 0 jest poprawne wydrukowane jako pierwsze. Kolejność nie może się różnić, jest dobrze zdefiniowana. – stefan

0

Jest to oczekiwane zachowanie, ponieważ inicjalizacja członka odbywa się przed treścią konstruktora. Dla realizacji tego warto dodać inicjatorów członka, jak również:

template<typename T> 
struct B 
{ 
    A a; 
    T b; 
    B() 
    : 
     a(), 
     b() 
    { 
     std::cout<<"B()"<<std::endl; 
    } 
}; 

Aby w pełni zrozumieć kolejność wykonywania, dodajmy obojętne pole całkowitą. Dodałem również szablon do wyświetlenia zagnieżdżenia. Zobacz wersję demonstracyjną: http://ideone.com/KylQQb.

#include <cstdio> 

struct A 
{ 
    A() 
    : 
    dummy(printf("Starting to construct A()\n")) 
    { 
     printf("Fully constructed A()\n"); 
    } 
    int dummy; 
}; 

template <typename T> 
struct Nesting; 

template <> 
struct Nesting<int> 
{ 
    constexpr static int value = 0; 
}; 

template <template <typename> class T, typename I> 
struct Nesting<T<I>> 
{ 
     constexpr static int value = 1 + Nesting<I>::value; 
}; 

template<typename T> 
struct B 
{ 
    int dummy; 
    A a; 
    T b; 
    B() 
    : 
    dummy(printf("Starting to construct B() with nesting %d\n", Nesting<B<T>>::value)), 
    a(), 
    b() 
    { 
     printf("Fully constructed B() with nesting %d\n", Nesting<B<T>>::value); 
    } 
}; 

int main() 
{ 
    B<B<B<int>>> Test; 
    return 0; 
} 

Wyjście to będzie

Starting to construct B() with nesting 3 
Starting to construct A() 
Fully constructed A() 
Starting to construct B() with nesting 2 
Starting to construct A() 
Fully constructed A() 
Starting to construct B() with nesting 1 
Starting to construct A() 
Fully constructed A() 
Fully constructed B() with nesting 1 
Fully constructed B() with nesting 2 
Fully constructed B() with nesting 3 
+0

Myślę, że jesteś trochę zdezorientowany. Konstruktory nie zaczynają być wywoływane, aw środku są wywoływane inne konstruktory. Konstruktory są jedno wątkowe i szeregowane dla jednego obiektu, który ma być w pełni skonstruowany. –

+0

@LuisColorado Nie jestem wcale zdezorientowany. Może nazywanie nie jest takie wspaniałe, ale tak właśnie się dzieje i co definiuje standard. – stefan

Powiązane problemy