2017-09-21 75 views
6

Zastanawiam się, dlaczego w następującym kodzie C++ konstruktor kopiowania jest nazywany 25 razy na 10 iteracji?Dlaczego konstruktor kopiowania jest wywoływany 25 razy, a pętla wstawiania iteruje tylko 10 razy?

Jeśli było 10, to OK 10/10 = 1, lub 20/10 = 2, lub 30/10 = 3, ale 25/10 = 2.5? Co oznacza tutaj .5?

Header:

class Person 
{ 
public: 
    Person(std::string name, int age); 
    Person(const Person &person); 

    const std::string &getName() const; 
    int getAge() const; 

private: 
    std::string name; 
    int age; 
}; 

Źródło:

Person::Person(string name, int age) : name(std::move(name)), age(age) 
{} 

Person::Person(const Person &person) 
{ 
    this->name = person.name; 
    this->age = person.age; 
    static int count = 0; 
    count++; 
    cout << ">>Copy-Person::Person(Person &person) " << count << endl; 
} 

const string &Person::getName() const 
{ 
    return name; 
} 

int Person::getAge() const 
{ 
    return age; 
} 

Zastosowanie:

int main() 
{ 
    vector<Person> persons; 

    for (int i = 0; i < 10; ++i) 
    { 
     Person person(to_string(i + 1), i); 
     persons.push_back(person); 
    } 
    cout << "-----------------------------------------------" << endl; 
    for (Person &person : persons) 
    { 
     cout << "name = " << person.getName() << " age = " << person.getAge() << endl; 
    } 
    return 0; 
} 

Wyjście:

>>Copy-Person::Person(Person &person) 1 
>>Copy-Person::Person(Person &person) 2 
>>Copy-Person::Person(Person &person) 3 
>>Copy-Person::Person(Person &person) 4 
>>Copy-Person::Person(Person &person) 5 
>>Copy-Person::Person(Person &person) 6 
>>Copy-Person::Person(Person &person) 7 
>>Copy-Person::Person(Person &person) 8 
>>Copy-Person::Person(Person &person) 9 
>>Copy-Person::Person(Person &person) 10 
>>Copy-Person::Person(Person &person) 11 
>>Copy-Person::Person(Person &person) 12 
>>Copy-Person::Person(Person &person) 13 
>>Copy-Person::Person(Person &person) 14 
>>Copy-Person::Person(Person &person) 15 
>>Copy-Person::Person(Person &person) 16 
>>Copy-Person::Person(Person &person) 17 
>>Copy-Person::Person(Person &person) 18 
>>Copy-Person::Person(Person &person) 19 
>>Copy-Person::Person(Person &person) 20 
>>Copy-Person::Person(Person &person) 21 
>>Copy-Person::Person(Person &person) 22 
>>Copy-Person::Person(Person &person) 23 
>>Copy-Person::Person(Person &person) 24 
>>Copy-Person::Person(Person &person) 25 
----------------------------------------------- 
name = 1 age = 0 
name = 2 age = 1 
name = 3 age = 2 
name = 4 age = 3 
name = 5 age = 4 
name = 6 age = 5 
name = 7 age = 6 
name = 8 age = 7 
name = 9 age = 8 
name = 10 age = 9 
+3

To jest realokacja. Prawdopodobnie z implementacji, która podwaja pojemność za każdym razem, gdy się przenosi. 1 + 2 + 4 + 8 = 15. –

+0

Nieparzysta liczba pochodzi z ponownej alokacji pamięci wewnętrznej wektora. – Rene

+0

@ T.C. Tak masz rację. Dziękuję Ci! –

Odpowiedz

4

Nie jesteś zastrzegając żadnej pamięci o swojej persons wektorze. Oznacza to, że gdy persons.size() == persons.capacity() podczas push_back, wektor przydzieli nowy większy bufor na stercie i skopiuje do niego każdy element. Dlatego widzisz więcej kopii niż się spodziewano.

Jeśli piszesz ...

persons.reserve(10); 

... przed pętli, nie będzie widać żadnych "ekstra" egzemplarz.

live example on wandbox


pamiętać, że można uniknąć altogheter kopii za pomocą zarówno std::vector::emplace_back i std::vector::reserve:

for (int i = 0; i < 10; ++i) 
{ 
    persons.emplace_back(to_string(i + 1), i); 
} 

będzie to tylko druk:

name = 1 wiek = 0

nazwa = 2 Wiek = 1

nazwa = 3 Wiek = 2

nazwa = 4 wiek = 3

nazwa = 5 wiek = 4

nazwa = 6 wiek = 5

nazwa = 7 wiek = 6

nazwa = 8 wiek = 7

nazwa = 9 Wiek = 8

nazwa = 10 lat = 9

live example on wandbox

+0

jest realokacja zawsze kopii do innej lokalizacji pamięci? czy może się zdarzyć, że jest trochę wolnej pamięci zaraz po zajmowaniu przez elementy, tak że nie trzeba ich kopiować? – user463035818

+0

@ tobi303: gdyby tak było, to nie musiałbyś realokować ... –

+0

Naprawdę, moje sformułowanie było wyłączone. Kolejna próba: czy możliwe jest zwiększenie wydajności bez konieczności ponownego przydzielania? Btw dla 'emplace_back' imho powinieneś wspomnieć, że potrzebuje' reserve' plus 'emplace_back'. Atm może być źle zrozumiany jako 'emplace_back' sam zapobiegając wszystkim kopiom – user463035818

2

Kiedy nowe size()>capacity() z vector, przesunięcie stanie. Wszystkie elementy zostaną skopiowane do nowej pamięci wewnętrznej, a następnie konstruktor kopiowania będzie wywoływany z bieżącą liczbą razy. Szczegóły dotyczące zwiększania pojemności zależą od implementacji. Wydaje się, że używana przez nią implementacja zwiększa dwukrotnie pojemność przy każdej realokacji. więc

#iterator current size capacity times of the copy (for reallocatioin + for push_back) 
1   0    0   0 + 1    
2   1    1   1 + 1    
3   2    2   2 + 1    
4   3    4   0 + 1    
5   4    4   4 + 1    
6   5    8   0 + 1    
7   6    8   0 + 1    
8   7    8   0 + 1    
9   8    8   8 + 1    
10  9    16  0 + 1    

Dlatego otrzymałeś wynik 25 razy.

Jako @VittorioRomeo wyjaśniono, można użyć std::vector::reserve, aby uniknąć ponownego przydziału.

2

Gdy std::vector::size() osiąga std::vector::capacity(), std::vector będzie miejsce dla nowych obiektów przydzielania nowy większy bufor o większej pojemności i kopiowanie uprzednio składowanych do nowego buforu.
Spowoduje to wywołanie nowych wywołań konstruktora kopiowania dla klasy Person (wypróbowałem twój kod za pomocą VS2015, a otrzymałem 35 wywołań konstruktora kopii).

zauważyć, że jeśli zarezerwować wystarczająco dużo miejsca w std::vector z metodą reserve(), dostajesz dokładnie 10 kopia konstruktora nazywa:

vector<Person> persons; 

// Reserve room in the vector to store 10 persons 
persons.reserve(10); 

for (int i = 0; i < 10; ++i) 
{ 
    Person person(to_string(i + 1), i); 
    persons.push_back(person); 
} 

To dlatego, że w tym przypadku wykonane wystarczająco dużo miejsca w wektorze więc rozmiar wektora nie przekracza jego pojemności (więc nie ma potrzeby przydzielania nowego większego bufora i kopiowania starych danych do tego nowego bufora).

Wszystko, co zostało powiedziane, jeśli klasa Person jest move-constructible, std::vector będzie ruch wcześniej utworzonego Person obiektów zamiast ich kopiowanie, który jest szybszy.

Jeśli dodać tę linię wewnątrz klasy Person:

class Person 
{ 
    public: 
    ... 

    // Synthesize default move constructor 
    Person(Person&&) = default; 
    ... 
}; 

dostaniesz dokładnie 10 połączeń konstruktor kopiujący, nawet jeśli nie wywołać metodę vector::reserve().

+1

Tak, to był dobry punkt, o którym wspomniałeś. Dzięki! –

+0

@BahramdunAdil: Dziękuję za interesujące pytanie! –