Czy dni przekazywania const std:: string & jako parametr są skończone?

Słyszałem niedawną rozmowę Herba Suttera, który zasugerował, że powody do odejścia std::vector i std::string przez const & w dużej mierze zniknęły. Zasugerował, że pisanie funkcji takiej jak następująca jest teraz lepsze:

std::string do_something ( std::string inval )
{
   std::string return_val;
   // ... do stuff ...
   return return_val;
}

Rozumiem, że return_val będzie wartością R w punkcie, w którym funkcja zwraca i dlatego może być zwrócona za pomocą semantyki move, która jest bardzo tania. Jednak inval jest nadal znacznie większy niż rozmiar odniesienia (który jest zwykle zaimplementowany jako wskaźnik). To jest ponieważ std::string ma różne komponenty, w tym wskaźnik do sterty i element {[7] } do optymalizacji krótkich łańcuchów. Wydaje mi się więc, że przechodzenie przez odniesienie to nadal dobry pomysł.

Czy ktoś może wyjaśnić, dlaczego Herb mógł to powiedzieć?
 521
Author: Mateen Ulhaq, 2012-04-19

13 answers

Powodem, dla którego Herb powiedział to, co powiedział, są takie przypadki.

Załóżmy, że mam funkcję A która wywołuje funkcję B, która wywołuje funkcję C. I A przechodzi przez B i do C. A nie wie ani nie dba o C; Wszystko A wie o B. Oznacza to, że {[5] } jest szczegółem implementacji B.

Powiedzmy, że A jest zdefiniowane następująco:

void A()
{
  B("value");
}

Jeśli B I C biorą łańcuch przez const&, to wygląda to tak to:

void B(const std::string &str)
{
  C(str);
}

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}
Wszystko dobrze i dobrze. Po prostu przekazujesz wskazówki, żadnego kopiowania, żadnego poruszania się, wszyscy są szczęśliwi. C bierze const& ponieważ nie przechowuje ciągu. Po prostu go używa.

Teraz chcę dokonać jednej prostej zmiany: C musi gdzieś przechowywać ciąg znaków.

void C(const std::string &str)
{
  //Do something with `str`.
  m_str = str;
}

Witam, Konstruktor kopiujący i potencjalna alokacja pamięci (ignoruj Short String Optimization (SSO) ). Semantyka ruchu C++11 ma umożliwić usunięcie niepotrzebne kopiowanie, prawda? I A przekazuje tymczasowe; nie ma powodu, dla którego Cpowinien kopiować dane. Powinien po prostu uciec z tym, co zostało mu dane.

Tylko, że nie może, ponieważ wymaga const&.

Jeśli zmienię C, aby wziąć jego parametr po wartości, to po prostu spowoduje, że B zrobi kopię do tego parametru; nic nie zyskam.

Więc gdybym tylko przeszedł str przez wartość przez wszystkie funkcje, opierając się na std::move aby przetasować danych wokół, nie mielibyśmy tego problemu. Jeśli ktoś chce to zatrzymać, może. Jeśli nie, to dobrze.

Czy jest droższy? Tak; przejście do wartości jest droższe niż korzystanie z referencji. Czy jest tańszy niż Kopia? Nie dla małych strun z SSO. Czy warto to robić?

To zależy od Twojego przypadku użycia. Jak bardzo nienawidzisz alokacji pamięci?

 354
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:24

Czy dni przekazywania const std:: string & jako parametr są skończone?

Nie . Wiele osób korzysta z tej porady (w tym Dave Abrahams) poza domeną, której dotyczy, i upraszcza ją, aby zastosować się do {12]}wszystkich std::string parametry -- zawsze przekazywanie std::string przez wartość nie jest "najlepszą praktyką" dla dowolnych dowolnych parametrów i aplikacji, ponieważ optymalizacje, na których te rozmowy/artykuły koncentrują się, dotyczą tylko ograniczonego zbioru przypadki .

Jeśli zwracasz wartość, mutujesz parametr lub przyjmujesz wartość, przekazywanie przez wartość może zaoszczędzić kosztowne kopiowanie i zapewnić wygodę składniową.

Jak zwykle, przejście przez const reference oszczędza wiele kopii , gdy nie potrzebujesz kopii.

Teraz do konkretnego przykładu:

Jednak inval jest nadal dużo większy niż rozmiar odniesienia (który jest zwykle zaimplementowany jako wskaźnik). Dzieje się tak dlatego, że std:: string ma różne komponenty, w tym wskaźnik do sterty i znak pręta [] do optymalizacji krótkich łańcuchów. Wydaje mi się więc, że przechodzenie przez odniesienie to nadal dobry pomysł. Czy ktoś może wyjaśnić, dlaczego Herb mógł to powiedzieć?

Jeśli rozmiar stosu jest problemem (i zakładając, że nie jest to inlined / optimized), return_val + inval > return_val -- IOW, użycie stosu szczytowego może być zmniejszone przez podanie tutaj wartości (Uwaga: uproszczenie ABIs). Tymczasem przejście przez const reference może wyłączyć optymalizacje. Głównym powodem nie jest unikanie wzrostu stosu, ale zapewnienie optymalizacji może być przeprowadzone , gdzie ma to zastosowanie.

Dni mijania const reference jeszcze się nie skończyły-zasady są po prostu bardziej skomplikowane niż kiedyś. Jeśli wydajność jest ważna, warto zastanowić się, jak przekazać te typy, na podstawie szczegółów używanych w swoich implementacjach.

 127
Author: justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 21:28:12

To w dużej mierze zależy od implementacji kompilatora.

Jednak zależy to również od tego, czego używasz.

Rozważmy następne funkcje:

bool foo1( const std::string v )
{
  return v.empty();
}
bool foo2( const std::string & v )
{
  return v.empty();
}

Te funkcje są zaimplementowane w oddzielnej jednostce kompilacji w celu uniknięcia inliningu. Następnie:
1. Jeśli przekażesz dosłowność do tych dwóch funkcji, nie zobaczysz dużej różnicy w występach. W obu przypadkach należy utworzyć obiekt string
2. Jeśli przekażesz inny obiekt std:: string, foo2 będzie lepszy foo1, ponieważ foo1 zrobi głęboką kopię.

Na moim komputerze, używając g++ 4.6.1, mam takie wyniki:

  • zmienna według referencji: 1000000000 iteracji - > czas upłynął: 2.25912 sek
  • zmienna według wartości: 1000000000 iteracji - > czas upłynął: 27.2259 sek
  • {{100000000} - > czas upłynął: 9.10319 sek
  • dosłowne według wartości: 100000000 iteracji - > czas upłynął: 8.62659 sek
 55
Author: BЈовић,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-19 16:05:05

Jeśli nie potrzebujesz kopii, nadal rozsądnie jest wziąć const &. Na przykład:

bool isprint(std::string const &s) {
    return all_of(begin(s),end(s),(bool(*)(char))isprint);
}

Jeśli zmienisz to, aby przyjmować ciąg znaków według wartości, w końcu przeniesiesz lub skopiujesz parametr i nie ma takiej potrzeby. Nie tylko kopiowanie / przenoszenie jest prawdopodobnie droższe, ale również wprowadza nową potencjalną awarię; kopiowanie / przenoszenie może rzucić wyjątek (np. alokacja podczas kopiowania może się nie powieść), podczas gdy odwołanie do istniejącej wartości nie może.

If you do potrzebujesz kopii, a następnie przekazywanie i zwracanie przez wartość jest zwykle (zawsze?) najlepszym rozwiązaniem. W rzeczywistości generalnie nie martwiłbym się o to w C++03, chyba że okaże się, że dodatkowe kopie faktycznie powodują problem z wydajnością. Copy elision wydaje się dość wiarygodne na współczesnych kompilatorach. Myślę, że sceptycyzm ludzi i upór, że trzeba sprawdzić tabelę wsparcia kompilatora dla RVO jest w większości przestarzały w dzisiejszych czasach.


W skrócie, C++11 tak naprawdę nic nie zmienia w tym zakresie z wyjątkiem ludzie, którzy nie ufali copy elision.

 41
Author: bames53,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-19 15:50:31

Krótka odpowiedź: nie! Długa odpowiedź:

  • Jeśli nie chcesz modyfikować ciągu znaków (treat jest tylko do odczytu), przekaż go jako const ref&.
    (funkcja ta musi oczywiście pozostawać w zasięgu, podczas gdy funkcja, która go używa, wykonuje)
  • jeśli planujesz ją zmodyfikować lub wiesz, że wyjdzie poza zakres (wątki) , przekaż ją jako value, nie kopiuj const ref& wewnątrz twojego ciała funkcji.

Był post na cpp-next.com called "chcesz prędkości, podaj wartość!". TL; DR:

Guideline: nie kopiuj argumentów funkcji. Zamiast tego przekaż je według wartości i pozwól kompilatorowi wykonać kopiowanie.

Tłumaczenie ^

Nie kopiuj argumentów funkcji - - - oznacza: jeśli planujesz zmodyfikować wartość argumentu kopiując ją do zmiennej wewnętrznej, po prostu użyj argumentu wartości.

Więc, nie rób to:

std::string function(const std::string& aString){
    auto vString(aString);
    vString.clear();
    return vString;
}

Do this :

std::string function(std::string aString){
    aString.clear();
    return aString;
}

Kiedy trzeba zmodyfikować wartość argumentu w ciele funkcji.

musisz tylko być świadomy, jak planujesz użyć argumentu w ciele funkcji. Tylko do odczytu czy nie... i jeśli będzie w zasięgu.

 41
Author: CodeAngry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-27 13:59:05

Prawie.

W C++17 mamy basic_string_view<?>, co sprowadza nas do jednego wąskiego przypadku użycia dla std::string const& parametrów.

Istnienie semantyki move wyeliminowało jeden przypadek użycia std::string const& -- jeśli planujesz zapisać parametr, pobranie std::string według wartości jest bardziej optymalne, jak możesz move z parametru.

Jeśli ktoś wywołał twoją funkcję z surowym C "string" oznacza to, że tylko jeden bufor std::string jest kiedykolwiek alokowany, w przeciwieństwie do dwóch w std::string const& case.

Jeśli jednak nie zamierzasz wykonać kopii, wykonywanie przez {[1] } jest nadal przydatne w C++14.

Z std::string_view, tak długo, jak nie przekazujesz tego ciągu do API, które oczekuje zakończonych buforami znaków w stylu C, możesz efektywniej uzyskać std::string podobną funkcjonalność bez ryzyka alokacji. Surowy ciąg C może być nawet zamieniony w std::string_view bez alokacji lub kopiowania znaków.

W tym momencie, użycie dla std::string const& jest wtedy, gdy nie kopiujesz hurtownia danych i zamierzają przekazać je do API w stylu C, które oczekuje zakończonego buforem null, i potrzebujesz funkcji łańcuchowych wyższego poziomu, które std::string zapewnia. W praktyce jest to rzadki zestaw wymagań.

 19
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-11 09:30:03

std::string nie jest zwykłymi starymi danymi (POD) , a jego surowy rozmiar nie jest najistotniejszą rzeczą w historii. Na przykład, jeśli przekazujesz ciąg znaków, który jest powyżej długości SSO i przydzielony na stercie, oczekuję, że Konstruktor kopiujący nie skopiuje magazynu SSO.

Jest to zalecane, ponieważ inval jest skonstruowany z wyrażenia argumentu, a więc jest zawsze przenoszony lub kopiowany odpowiednio - nie ma utraty wydajności, zakładając, że potrzebujesz własności kłótnia. Jeśli tego nie zrobisz, odniesienie const nadal może być lepszym rozwiązaniem.

 16
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:03

Skopiowałem / wkleiłem odpowiedź z tego pytania tutaj i zmieniłem nazwy i pisownię, aby pasowały do tego pytania.

Oto kod do pomiaru tego, co jest pytane:

#include <iostream>

struct string
{
    string() {}
    string(const string&) {std::cout << "string(const string&)\n";}
    string& operator=(const string&) {std::cout << "string& operator=(const string&)\n";return *this;}
#if (__has_feature(cxx_rvalue_references))
    string(string&&) {std::cout << "string(string&&)\n";}
    string& operator=(string&&) {std::cout << "string& operator=(string&&)\n";return *this;}
#endif

};

#if PROCESS == 1

string
do_something(string inval)
{
    // do stuff
    return inval;
}

#elif PROCESS == 2

string
do_something(const string& inval)
{
    string return_val = inval;
    // do stuff
    return return_val; 
}

#if (__has_feature(cxx_rvalue_references))

string
do_something(string&& inval)
{
    // do stuff
    return std::move(inval);
}

#endif

#endif

string source() {return string();}

int main()
{
    std::cout << "do_something with lvalue:\n\n";
    string x;
    string t = do_something(x);
#if (__has_feature(cxx_rvalue_references))
    std::cout << "\ndo_something with xvalue:\n\n";
    string u = do_something(std::move(x));
#endif
    std::cout << "\ndo_something with prvalue:\n\n";
    string v = do_something(source());
}

Dla mnie to:

$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=1 test.cpp
$ a.out
do_something with lvalue:

string(const string&)
string(string&&)

do_something with xvalue:

string(string&&)
string(string&&)

do_something with prvalue:

string(string&&)
$ clang++ -std=c++11 -stdlib=libc++ -DPROCESS=2 test.cpp
$ a.out
do_something with lvalue:

string(const string&)

do_something with xvalue:

string(string&&)

do_something with prvalue:

string(string&&)

Poniższa tabela podsumowuje moje wyniki (używając clang-std=c++11). Pierwsza liczba to liczba konstrukcji kopiujących, a druga liczba to liczba konstrukcji ruchomych:

+----+--------+--------+---------+
|    | lvalue | xvalue | prvalue |
+----+--------+--------+---------+
| p1 |  1/1   |  0/2   |   0/1   |
+----+--------+--------+---------+
| p2 |  1/0   |  0/1   |   0/1   |
+----+--------+--------+---------+

Rozwiązanie pass-by-value wymaga tylko jednego przeciążenia, ale kosztuje dodatkowa konstrukcja ruchu podczas przechodzenia wartości LV i xvalue. Może to być, ale nie może być dopuszczalne w danej sytuacji. Oba rozwiązania mają zalety i wady.

 15
Author: Howard Hinnant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:48

Herb Sutter jest nadal w rejestrze, wraz z Bjarne Stroustroup, zalecając const std::string& jako typ parametru; zobacz https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in .

Istnieje pułapka, o której nie wspomniano w żadnej z innych odpowiedzi tutaj: jeśli przekażesz literalny łańcuch znaków do parametru const std::string&, przekaże on odniesienie do tymczasowego łańcucha, utworzonego w locie, aby trzymać znaki literała. Jeśli następnie zapisać to odniesienie, będzie to nieprawidłowy po dealokacji tymczasowego łańcucha. Aby być bezpiecznym, musisz zapisać kopię , a nie odniesienie. Problem wynika z faktu, że literały ciągów są typami const char[N], wymagającymi promocji do std::string.

Poniższy kod ilustruje pułapkę i obejście, wraz z niewielką opcją wydajności -- przeciążenie metodą const char*, jak opisano w czy istnieje sposób, aby przekazać ciąg znaków jako odniesienie w C++.

(Uwaga: Sutter & Stroustroup radzą że jeśli zachowasz kopię ciągu znaków, podaj również przeciążoną funkcję z parametrem & & i STD:: move ().)

#include <string>
#include <iostream>
class WidgetBadRef {
public:
    WidgetBadRef(const std::string& s) : myStrRef(s)  // copy the reference...
    {}

    const std::string& myStrRef;    // might be a reference to a temporary (oops!)
};

class WidgetSafeCopy {
public:
    WidgetSafeCopy(const std::string& s) : myStrCopy(s)
            // constructor for string references; copy the string
    {std::cout << "const std::string& constructor\n";}

    WidgetSafeCopy(const char* cs) : myStrCopy(cs)
            // constructor for string literals (and char arrays);
            // for minor efficiency only;
            // create the std::string directly from the chars
    {std::cout << "const char * constructor\n";}

    const std::string myStrCopy;    // save a copy, not a reference!
};

int main() {
    WidgetBadRef w1("First string");
    WidgetSafeCopy w2("Second string"); // uses the const char* constructor, no temp string
    WidgetSafeCopy w3(w2.myStrCopy);    // uses the String reference constructor
    std::cout << w1.myStrRef << "\n";   // garbage out
    std::cout << w2.myStrCopy << "\n";  // OK
    std::cout << w3.myStrCopy << "\n";  // OK
}

Wyjście:

const char * constructor
const std::string& constructor

Second string
Second string
 13
Author: circlepi314,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:32

IMO używanie referencji C++ dla std::string jest szybką i krótką optymalizacją lokalną, podczas gdy użycie wartości passing by Może być (lub nie) lepszą optymalizacją globalną.

Więc odpowiedź brzmi: to zależy od okoliczności:

  1. Jeśli piszesz cały kod z zewnątrz do wewnątrz funkcji, wiesz, co robi Kod, możesz użyć referencji const std::string &.
  2. Jeśli piszesz kod biblioteki lub używasz mocno kodu biblioteki, w której przekazywane są ciągi znaków, prawdopodobnie zyskasz więcej w globalnym wyczuwaj, ufając std::string zachowaniu konstruktora kopiującego.
 7
Author: digital_infinity,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-25 12:03:06

Zobacz "Herb Sutter" powrót do podstaw! Podstawy nowoczesnego stylu C++ " . Wśród innych tematów, przegląda parametr przekazując porady, które zostały podane w przeszłości, i nowe pomysły, które przychodzą w C++11 i konkretnie patrzy na pomysł przekazywania ciągów przez wartość.

slide 24

Benchmarki pokazują, że przekazywanie std::strings przez wartość, w przypadkach, gdy funkcja i tak ją skopiuje, może być znacznie wolniejsze!

To dlatego, że zmuszasz go do zawsze wykonaj pełną kopię (a następnie przenieś na swoje miejsce), podczas gdy wersja const& zaktualizuje Stary ciąg znaków, który może ponownie użyć już przydzielonego bufora.

Zobacz jego slajd 27: dla funkcji" set " opcja 1 jest taka sama jak zawsze. Opcja 2 dodaje przeciążenie dla odniesienia rvalue, ale daje to wybuch kombinatoryczny, jeśli istnieje wiele parametrów.

To tylko dla parametrów "sink", gdzie musi być utworzony łańcuch znaków (nie zmieniając jego istniejącej wartości), że Trick pass-by-value jest ważne. Oznacza to, że konstruktory, w których parametr bezpośrednio inicjuje element dopasowującego typu.

Jeśli chcesz zobaczyć, jak głęboko możesz się tym martwić, obejrzyj prezentację Nicolai Josuttisa i powodzenia z tym ("Perfect - Done!" n razy po wykryciu błędu w poprzedniej wersji. Byłeś tam kiedyś?)


Jest to również podsumowane jako ⧺F. 15 w standardowych wytycznych.

 3
Author: JDługosz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-09 01:25:53

Jak zaznacza @JDługosz w komentarzach, Herb daje inne rady w innym (później?) talk, see mniej więcej stąd: https://youtu.be/xnqTKD8uD64?t=54m50s .

Jego rada sprowadza się tylko do użycia parametrów wartości dla funkcji f, która pobiera tzw. argumenty sink, zakładając, że przeniesiesz konstrukcję z tych argumentów sink.

To ogólne podejście dodaje tylko narzut konstruktora ruchu zarówno dla argumentów lvalue, jak i rvalue w porównaniu z optymalnym implementacja f dostosowana odpowiednio do argumentów lvalue i rvalue. Aby zobaczyć, dlaczego tak jest, Załóżmy, że f pobiera parametr wartości, gdzie T jest pewnym typem konstruktywnym typu copy and move:

void f(T x) {
  T y{std::move(x)};
}

Wywołanie f z argumentem lvalue spowoduje, że Konstruktor kopiujący zostanie wywołany do konstruowania x, a konstruktor move zostanie wywołany do konstruowania y. Z drugiej strony, wywołanie f z argumentem rvalue spowoduje wywołanie konstruktora move do konstruowania x, oraz inny konstruktor ruchu, który ma być wywołany do konstruowania y.

Ogólnie, optymalna implementacja f dla argumentów lvalue jest następująca:

void f(const T& x) {
  T y{x};
}

W tym przypadku tylko jeden Konstruktor kopiujący jest wywoływany do konstruowania y. Optymalna implementacja f dla argumentów rvalue jest, ogólnie rzecz biorąc, następująca:

void f(T&& x) {
  T y{std::move(x)};
}

W tym przypadku tylko jeden konstruktor ruchu jest wywoływany do konstruowania y.

Więc rozsądnym kompromisem jest przyjęcie wartości parametr i mają jeden dodatkowy konstruktor ruchu wywołujący argumenty lvalue lub rvalue w odniesieniu do optymalnej implementacji, co jest również radą udzieloną w rozmowie Herba.

Jak zauważył @JDługosz w komentarzach, przekazywanie przez wartość ma sens tylko dla funkcji, które zbudują jakiś obiekt z argumentu sink. Jeśli masz funkcję f, która kopiuje jej argument, podejście pass-by-value będzie miało więcej narzutu niż ogólne podejście pass-by-const-reference. Metoda pass-by-value dla funkcji f, która zachowuje kopię swojego parametru, będzie miała postać:

void f(T x) {
  T y{...};
  ...
  y = std::move(x);
}

W tym przypadku istnieje konstrukcja kopiująca i przypisanie move dla argumentu lvalue oraz konstrukcja move i przypisanie move dla argumentu rvalue. Najbardziej optymalnym przypadkiem argumentu lvalue jest:

void f(const T& x) {
  T y{...};
  ...
  y = x;
}

Sprowadza się to tylko do przypisania, które jest potencjalnie znacznie tańsze niż Konstruktor kopiujący plus przypisanie ruchu wymagane do podejście pass-by-value. Powodem tego jest to, że przypisanie może ponownie wykorzystać istniejącą przydzieloną pamięć w y, a tym samym zapobiec (de)alokacji, podczas gdy Konstruktor kopiujący zwykle przydziela pamięć.

Dla argumentu rvalue najbardziej optymalna implementacja dla f, która zachowuje kopię ma postać:

void f(T&& x) {
  T y{...};
  ...
  y = std::move(x);
}

Więc, w tym przypadku tylko przeniesienie. Przekazanie wartości R do wersji f, która pobiera referencję const, kosztuje tylko przypisanie zamiast przesuń zadanie. Relatywnie rzecz biorąc, wersja f przyjmująca odniesienie const w tym przypadku jako ogólna implementacja jest preferowana.

Więc ogólnie rzecz biorąc, aby uzyskać najbardziej optymalną implementację, będziesz musiał przeciążyć lub zrobić jakieś idealne przekierowanie, jak pokazano w dyskusji. Wadą jest kombinatoryczna eksplozja liczby wymaganych przeciążeń, w zależności od liczby parametrów dla f W przypadku, gdy zdecydujesz się na przeciążenie kategorii wartości argumentu. Perfect przekierowanie ma tę wadę, że f staje się funkcją szablonu, co uniemożliwia zrobienie go wirtualnym, a skutkuje znacznie bardziej złożonym kodem, jeśli chcesz uzyskać go w 100% poprawnie(zobacz rozmowę o krwawych szczegółach).

 2
Author: Ton van den Heuvel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-27 13:48:56

Problem polega na tym, że" const " jest kwalifikatorem nie-ziarnistym. To, co zwykle oznacza "const string ref", to "nie modyfikuj tego łańcucha", a Nie"Nie modyfikuj liczby referencji". Po prostu nie ma sposobu, aby w C++ powiedzieć , Które członkowie są "const". Albo wszyscy są, albo żaden z nich nie jest.

W celu zhakowania tego problemu językowego, STL może pozwolić "C ()" w twoim przykładzie Na wykonanie move-semantycznej kopii w każdym razie i posłusznie ignorować "const" z regard to the reference count (mutable). Tak długo, jak to było dobrze sprecyzowane, to byłoby w porządku.

Ponieważ STL tego nie robi, mam wersję napisu, który const_casts usuwa licznik referencji (nie ma możliwości retroaktywnego stworzenia czegoś zmiennego w hierarchii klas), I-lo i Beautiful-możesz swobodnie przekazywać cmstringi jako referencje const i robić ich kopie w głębokich funkcjach, przez cały dzień, bez wycieków lub problemów.

Ponieważ C++ nie oferuje " pochodnej klasy const" tutaj spisanie dobrej specyfikacji i zrobienie błyszczącego nowego obiektu" const movable string " (cmstring) jest najlepszym rozwiązaniem, jakie widziałem.

 1
Author: Erik Aronesty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-10 20:53:28