2013-07-14 11 views
18

Zastanawiam się, w jakich sytuacjach wciąż potrzebuję użyć odniesień stałych w parametrach od C++ 11. Nie w pełni rozumiem semantykę ruchu, ale myślę, że jest to uzasadnione pytanie. To pytanie jest przeznaczone tylko dla sytuacji, w których odniesienie do const zastępuje wykonywaną kopię, podczas gdy jest potrzebne tylko do "odczytania" wartości (np. Użycie funkcji składowych stałych).C++ 11 Odsyłacz do Constunkcji VS: semantyczny ruch

Normalnie chciałbym napisać (członek) funkcję tak:

#include <vector> 

template<class T> 
class Vector { 
    std::vector<T> _impl; 
public: 
    void add(const T& value) { 
     _impl.push_back(value); 
    } 
}; 

Ale myślę, że to jest bezpiecznie założyć, że do kompilatora by zoptymalizować go za pomocą semantyki move jeśli piszę to tak i class T ofcourse implementuje konstruktor ruchu:

#include <vector> 

template<class T> 
class Vector { 
    std::vector<T> _impl; 
public: 
    void add(T value) { 
     _impl.push_back(value); 
    } 
}; 

Mam rację? Jeśli tak, czy można bezpiecznie założyć, że można go zastosować w każdej sytuacji? Jeśli nie, chciałbym wiedzieć, który. Ułatwiłoby to życie, ponieważ na przykład nie musiałbym wprowadzać specjalizacji dla podstawowych typów, poza tym wygląda na znacznie czystsze.

+0

możliwy duplikat [Czy lepiej w C++ przekazywać wartości lub przekazywać przez stałe odniesienie?] (Http: // stackoverflow.com/questions/270408/is-it-it-better-in-c-to-pass-by-value-or-pass-by-constant-reference) – stijn

+1

'vector :: push_back' tworzy kopie. Szukasz 'emplace_back'. – Cubic

+3

@stijn Nie mogę znaleźć sytuacji similair dotyczącej semantyki ruchu w tym pytaniu. – Tim

Odpowiedz

19

rozwiązanie proponujecie:

void add(T value) { 
    _impl.push_back(value); 
} 

wymagałoby pewne korekty, ponieważ ten sposób nie zawsze kończy się wykonanie jednej kopii value, nawet jeśli przechodzą rvalue do add() (dwóch egzemplarzach, jeśli zdać lvalue): ponieważ value jest l-wartością, kompilator nie przeniesie się automatycznie z niego, gdy przekażesz go jako argument do push_back.

Zamiast tego, należy to zrobić:

void add(T value) { 
    _impl.push_back(std::move(value)); 
//     ^^^^^^^^^ 
} 

ten jest lepszy, ale nadal nie jest wystarczająco dobre dla kodu szablonu, ponieważ nie wiem, czy T jest tanie lub drogie, aby przenieść. Jeśli T jest POD tak:

struct X 
{ 
    double x; 
    int i; 
    char arr[255]; 
}; 

Następnie przesuwając go nie będzie szybciej niż kopiowanie (w rzeczywistości, przenosząc byłoby samo jak kopiowanie). Ponieważ twój ogólny kod powinien unikać niepotrzebnych operacji (a to dlatego, że te operacje mogą być drogie dla niektórych typów), nie możesz sobie pozwolić na przyjęcie parametru według wartości.

Możliwym rozwiązaniem (przyjmowana jest przez C++ średnia biblioteka) jest zapewnienie dwóch przeciążeniem add(), który toczy się odniesienie lwartości i który toczy odnośnik RValue:

void add(T const& val) { _impl.push_back(val); } 
void add(T&& val) { _impl.push_back(std::move(val)); } 

Inną możliwością jest zapewnienie (ewentualnie SFINAE ograniczonej) perfect-forwarding szablon wersja add() które akceptują tzw uniwersalne odniesienie (niestandardowych termin ukuty przez Scott Meyers):

template<typename U> 
void add(U&& val) { _impl.push_back(std::forward<U>(val)); } 

Oba te rozwiązania są optymalne w tym sensie, że wykonywana jest tylko jedna kopia, gdy podano wartości l, a tylko jeden ruch jest wykonywany, gdy podane są wartości r.

+0

Dzięki temu, czego potrzebowałem! O "ale nadal niewystarczająco dobre dla kodu szablonu" i "przeniesienie go byłoby tym samym, co skopiowanie go", czy nie jest dokładnie przeciwieństwem pierwszego zacytowanego stwierdzenia? Chodzi mi o to, czy nie jest to dokładnie to, co chcę osiągnąć, podstawowe typy bez obciążenia przy użyciu tej samej metody. – Tim

+0

@Tim: Nie widzę tam sprzeczności. "Nadal niewystarczająco dobre dla kodu szablonu" odnosi się do funkcji przyjmującej parametr według wartości i ogólnie nie jest wystarczająco dobre, ponieważ parametr może mieć typ, który może być drogi (przenieść lub skopiować), więc należy unikać każdy dodatkowy ruch (kopiowanie lub). 'X' ma stanowić przykład takiego typu (właściwie to, czy rzeczywiście jest" kosztowny ", zależy od wymagań klienta, ale nie pozwalajmy sobie na tego typu rozważania). –

+0

Rozumiem, co mówisz. Mogłem nie być jasne, czy 'std :: vector :: push_back' już robi kopię? Więc jeśli i tylko, jeśli 'std :: vector :: push_back' może przyjąć wartość przeniesioną lub skopiowaną, w przypadku typów podstawowych, bezpośrednio (co wątpię, ponieważ przyjmuje odniesienie do const), byłoby to wystarczająco dobre dla szablonu kod. Czy może czegoś brakuje? – Tim