2017-08-28 66 views
41

W języku C++ 98 prototyp konstruktora wypełniania std::vector ma domyślną wartość dla inicjalizatora.Dlaczego C++ 11 usunął domyślną wartość z prototypów konstruktora wypełnienia std :: vector?

explicit vector (size_type n, const value_type& val = value_type(), 
       const allocator_type& alloc = allocator_type()); 

C++ 11 wykorzystuje dwa prototypy.

explicit vector (size_type n); 
     vector (size_type n, const value_type& val, 
       const allocator_type& alloc = allocator_type()); 

(w C++ 14 konstruktor wypełnienie znowu zmienił, ale to nie o to chodzi z tym pytaniem.)

Link odniesienia jest here.

Dlaczego C++ 11 wycofał domyślną wartość inicjatora value_type()?

Przy okazji, próbowałem skompilować następujący kod z clang++ -std=c++11 i wystąpił błąd, co oznacza, że ​​typ wartości nadal musi mieć domyślny konstruktor, taki jak S() {}, tj. Być domyślnie konstruktywny.

#include <vector> 

struct S { 
    int k; 
    S(int k) : k(k) {} // intentionally remove the synthesized default constructor 
}; 

int main() { 
    std::vector<S> s(5); // error: no matching constructor 
} 
+3

Przykład pokazany na końcu nie działałby wcześniej przed C++ 11, ponieważ 'S' nie jest domyślnie konstruktywny. A C++ 11 nie * zniszczył * domyślnej wartości, że pojedynczy konstruktor został zastąpiony przez dwa inne. – Praetorian

+5

Domyślne wartości są złe. –

+0

@Praetorian tak, powinienem powiedzieć, że C++ 11 * usunięto * domyślną wartość z prototypów konstruktora. – user8385554

Odpowiedz

50

C++ 98 wziął obiekt prototypowy, a następnie skopiował go n razy. Domyślnie prototyp był obiektem domyślnym.

Wersja C++ 11 tworzy n obiektów skonstruowanych domyślnie.

Eliminuje to liczbę n kopii i zastępuje ją n domyślnymi konstrukcjami. Ponadto unika się konstruowania prototypu.

Załóżmy, że klasa wygląda następująco:

struct bulky { 
    std::vector<int> v; 
    bulky():v(1000) {} // 1000 ints 
    bulky(bulky const&)=default; 
    bulky& operator=(bulky const&)=default; 

    // in C++11, avoid ever having an empty vector to maintain 
    // invariants: 
    bulky(bulky&& o):bulky() { 
    std::swap(v, o.v); 
    } 
    bulky& operator=(bulky&& o) { 
    std::swap(v,o.v); 
    return *this; 
    } 
}; 

jest to klasa, która zawsze posiada bufor 1000int s.

gdybyśmy następnie utworzyć wektor bulky:

std::vector<bulky> v(2); 

w C++ 98 ten przeznaczono 3 razy 1000 całkowitymi. W C++ 11 przydzielono tylko 2 razy 1000 liczb całkowitych.

Ponadto wersja C++ 98 wymaga, aby typ był kopiowalny. Istnieją typy, które nie mogą być kopiowane w C++ 11, takie jak std::unique_ptr<T>, a vector domyślnie skonstruowanych unikatowych wskaźników nie może zostać wygenerowanych przy użyciu sygnatury C++ 98. Podpis C++ 11 nie ma z tym problemu.

std::vector<std::unique_ptr<int>> v(100); 

Powyższe nie działałoby, gdybyśmy nadal mieli wersję C++ 98.

+2

Prawdopodobnie powrócę do wyjaśnienia, ponieważ poprawność wpływa na wydajność: D Niemniej jednak, dobra odpowiedź na dobre pytanie. – SergeyA

+0

@SergeyA Nie jestem pewien, kiedy dokładnie wymagania dotyczące bycia w wektorze, gdzie są zrelaksowane z bycia na typie przekazywanym do wektora, a zamiast tego są sprzężone z dokładnymi zastosowanymi metodami. Różnica wydajności z pewnością pojawiła się w C++ 11; Nie jestem pewien, czy technicznie spełniony był zrelaksowany wymóg. Więc dokładnie opisałem dokładnie to, co wiedziałem, a potem wspomniałem, że gdzieś w C++ 11/14 prowadziło to również do luźniejszych wymagań dla typu (w praktyce w C++ 11, nie jestem pewien, czy standard faktycznie złagodził go przed C + +14, ponieważ nie pamiętam) – Yakk

+0

ładne wyjaśnienie, po prostu nie rozumiem, dlaczego w wersji C++ 98 prototypowy obiekt nie jest używany jako pierwszy element (tzn. Że w twoim przykładzie również przydzielono by tylko 2000 liczb całkowitych) – user463035818

47

Powodem, dla którego konstruktor został podzielony na dwie części, było wspieranie typów "tylko ruchowych", takich jak unique_ptr<T>.

Ten konstruktor:

vector(size_type n, const T& value, const Allocator& = Allocator()); 

wymaga T się skopiować constructible, ponieważ nT s musi być kopiowane z value aby zapełnić vector.

Ten konstruktor:

explicit vector(size_type n, const Allocator& = Allocator()); 

robi nie wymagają T należy skopiować constructible tylko domyślne constructible.

Drugi konstruktor pracuje unique_ptr<T>:

std::vector<std::unique_ptr<int>> s(5); 

natomiast były konstruktor nie.

Oto propozycja, która dokonaniu tej zmiany: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1858.html#23.2.4.1%20-%20vector%20constructors,%20copy,%20and%20assignment

I ten papier ma pewne uzasadnienie, chociaż jest wprawdzie trochę na lakonicznym stronie: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1771.html

FWIW, resize:

void resize(size_type sz, T c = T()); 

został podzielony na:

void resize(size_type sz); 
void resize(size_type sz, const T& c); 

z tego samego powodu. Pierwszy wymaga domyślnego, możliwego do skonstruowania, ale nie kopiowalnego, który można skonstruować (aby obsługiwać domyślne typy konstruktywne typu "tylko ruch"), a drugi wymaga kopiowania możliwego do skonstruowania.

Zmiany te nie były zgodne w 100% wstecz. W przypadku niektórych typów (np. Zliczonych inteligentnych wskaźników), tworzenie kopii z domyślnego konstruowanego obiektu nie jest takie samo jak domyślna konstrukcja. Jednak uznano, że korzyść ze wspierania typów typu "tylko ruch" jest warta kosztu tego pęknięcia API.

+0

Dobra uwaga. 'unique_ptr ' jest doskonałym przykładem niekopiowanej klasy. Niestety może być tylko jedna akceptowana odpowiedź. – user8385554

+3

rekompensujemy upvotes –

+3

To wydaje się być prawdziwą odpowiedzią, ponieważ wyjaśnia, * dlaczego * została wykonana. (Współcześnie przyjęta odpowiedź Yakka również o tym wspomina, ale krótko na bok). – jamesdlin