it-swarm.com.de

Sind die Tage der Übergabe von const std :: string & als Parameter vorbei?

Ich habe kürzlich einen Vortrag von Herb Sutter gehört, der vorschlug, dass die Gründe, std::vector und std::string durch const & zu bestehen, weitgehend weg sind. Er schlug vor, eine Funktion wie die folgende zu schreiben:

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

Ich verstehe, dass der return_val an dem Punkt, an dem die Funktion zurückgibt, ein R-Wert sein wird und daher unter Verwendung der Verschiebungssemantik zurückgegeben werden kann, die sehr billig ist. inval ist jedoch immer noch viel größer als die Größe einer Referenz (die normalerweise als Zeiger implementiert wird). Dies liegt daran, dass ein std::string verschiedene Komponenten enthält, einschließlich eines Zeigers auf den Heap und eines Members char[] für die Optimierung kurzer Zeichenfolgen. Es scheint mir also, dass es immer noch eine gute Idee ist, durch Referenz zu gehen.

Kann jemand erklären, warum Herb das gesagt haben könnte?

572
Benj

Der Grund, warum Herb sagte, was er sagte, ist wegen solcher Fälle.

Angenommen, ich habe die Funktion A, die die Funktion B aufruft, die die Funktion C aufruft. Und A übergibt einen String an B und an C. A kennt oder kümmert sich nicht um C; Alles, worüber A Bescheid weiß, ist B. Das heißt, C ist ein Implementierungsdetail von B.

Angenommen, A ist wie folgt definiert:

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

Wenn B und C den String von const& nehmen, sieht es ungefähr so ​​aus:

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

void C(const std::string &str)
{
  //Do something with `str`. Does not store it.
}

Alles schön und gut. Sie geben nur Zeiger weiter, kopieren nicht, bewegen sich nicht, alle sind glücklich. C nimmt einen const&, weil der String nicht gespeichert wird. Es benutzt es einfach.

Jetzt möchte ich eine einfache Änderung vornehmen: C muss den String irgendwo speichern.

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

Hallo, Kopierkonstruktor und mögliche Speicherzuordnung (ignorieren Sie Short String Optimization (SSO) ). Die Verschiebungssemantik von C++ 11 soll es ermöglichen, unnötige Kopienkonstruktionen zu entfernen, oder? Und A übergibt eine vorübergehende; Es gibt keinen Grund, warum C die Daten kopieren muss . Es sollte nur mit dem fliehen, was ihm gegeben wurde.

Außer es kann nicht. Weil es einen const& braucht.

Wenn ich C ändere, um seinen Parameter als Wert zu übernehmen, bewirkt dies nur, dass B die Kopie in diesen Parameter ausführt. Ich gewinne nichts.

Wenn ich str soeben alle Funktionen wertmäßig durchlaufen hätte und mich auf std::move verlassen hätte, um die Daten zu mischen, hätten wir dieses Problem nicht. Wenn jemand daran festhalten will, kann er es. Wenn nicht, na ja.

Ist es teurer Ja; Das Verschieben in einen Wert ist teurer als das Verwenden von Referenzen. Ist es günstiger als die Kopie? Nicht für kleine Saiten mit SSO. Lohnt es sich zu tun?

Das hängt von Ihrem Anwendungsfall ab. Wie sehr hassen Sie Speicherzuweisungen?

379
Nicol Bolas

Sind die Tage der Übergabe von const std :: string & als Parameter vorbei?

Nein. Viele Leute befolgen diesen Ratschlag (einschließlich Dave Abrahams) außerhalb der Domäne, für die er gilt, und vereinfachen ihn, um ihn auf alle std::string -Parameter anzuwenden - Immer std::string als Wert zu übergeben, ist keine "Best Practice" für alle beliebigen Parameter und Anwendungen, da die Optimierungen, auf die sich diese Vorträge/Artikel konzentrieren, nur für a gelten Eingeschränkte Anzahl von Fällen .

Wenn Sie einen Wert zurückgeben, den Parameter ändern oder den Wert übernehmen, kann die Übergabe eines Werts teures Kopieren ersparen und syntaktische Bequemlichkeit bieten.

Wie immer spart das Übergeben von const reference viel Kopieren , wenn Sie keine Kopie benötigen .

Nun zum konkreten Beispiel:

Inval ist jedoch immer noch viel größer als die Größe einer Referenz (die normalerweise als Zeiger implementiert wird). Dies liegt daran, dass ein std :: string verschiedene Komponenten enthält, einschließlich eines Zeigers auf den Heap und eines Mitglieds char [] für die Optimierung kurzer Strings. Es scheint mir also, dass es immer noch eine gute Idee ist, durch Referenz zu gehen. Kann jemand erklären, warum Herb das gesagt haben könnte?

Wenn die Stapelgröße ein Problem darstellt (und davon ausgegangen wird, dass dies nicht inliniert/optimiert ist), return_val + inval> return_val - IOW, kann die maximale Stapelnutzung betragen. reduziert , indem hier ein Wert übergeben wird (Anmerkung: Übervereinfachung von ABIs). In der Zwischenzeit kann das Übergeben von const reference die Optimierungen deaktivieren. Der Hauptgrund ist hier, das Stapelwachstum nicht zu vermeiden, sondern sicherzustellen, dass die Optimierung durchgeführt werden kann , wo dies anwendbar ist .

Die Zeiten, in denen man sich auf const bezog, sind noch lange nicht vorbei - die Regeln sind nur komplizierter als früher. Wenn die Leistung wichtig ist, sollten Sie anhand der Details, die Sie in Ihren Implementierungen verwenden, überlegen, wie Sie diese Typen übergeben.

150
justin

Dies hängt stark von der Implementierung des Compilers ab.

Es hängt jedoch auch davon ab, was Sie verwenden.

Betrachten wir die nächsten Funktionen:

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

Diese Funktionen sind in einer separaten Zusammenstellungseinheit implementiert, um Inlining zu vermeiden. Dann :
1. Wenn Sie diesen beiden Funktionen ein Literal übergeben, werden Sie keinen großen Leistungsunterschied feststellen. In beiden Fällen muss ein String-Objekt erstellt werden
2. Übergeben Sie ein anderes std :: string-Objekt, übertrifft foo2foo1, da foo1 eine tiefe Kopie erstellt.

Auf meinem PC mit g ++ 4.6.1 habe ich folgende Ergebnisse erhalten:

  • variable per Referenz: 1000000000 Iterationen -> verstrichene Zeit: 2,25912 Sek
  • variable nach Wert: 1000000000 Iterationen -> verstrichene Zeit: 27,2259 Sek
  • literal als Referenz: 100000000 Iterationen -> verstrichene Zeit: 9.10319 Sek
  • literal nach Wert: 100000000 Iterationen -> verstrichene Zeit: 8,62659 Sek
60
BЈовић

Kurze Antwort: NEIN! Lange Antwort:

  • Wenn Sie die Zeichenfolge nicht ändern (behandeln ist schreibgeschützt), übergeben Sie sie als const ref&.
    (der const ref& muss offensichtlich innerhalb des Gültigkeitsbereichs bleiben, während die von ihm verwendete Funktion ausgeführt wird)
  • Wenn Sie beabsichtigen, es zu ändern, oder wenn Sie wissen, dass es aus dem Geltungsbereich gerät (Threads) , übergeben Sie es als value, Kopieren Sie den const ref& nicht in Ihren Funktionskörper.

Es gab einen Beitrag auf cpp-next.com mit dem Namen "Willst du Geschwindigkeit, pass by value! " . Die TL; DR:

Richtlinie : Kopieren Sie Ihre Funktionsargumente nicht. Übergeben Sie sie stattdessen als Wert und lassen Sie den Compiler das Kopieren durchführen.

ÜBERSETZUNG von ^

Kopieren Sie Ihre Funktionsargumente nicht --- bedeutet: , wenn Sie den Argumentwert ändern möchten, indem Sie ihn in ein kopieren interne Variable, verwenden Sie stattdessen einfach ein Wertargument .

Also, mach das nicht :

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

mach das :

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

Wenn Sie den Argumentwert in Ihrem Funktionskörper ändern müssen.

Sie müssen nur wissen, wie Sie das Argument im Funktionskörper verwenden möchten. Schreibgeschützt oder NICHT ... und wenn es im Geltungsbereich bleibt.

47
CodeAngry

Es ist immer noch sinnvoll, const & zu nehmen, es sei denn, Sie benötigen tatsächlich eine Kopie. Zum Beispiel:

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

Wenn Sie dies ändern, um die Zeichenfolge nach Wert zu übernehmen, wird der Parameter am Ende verschoben oder kopiert, und das ist nicht erforderlich. Kopieren/Verschieben ist nicht nur wahrscheinlich teurer, sondern führt auch zu einem neuen potenziellen Fehler. Das Kopieren/Verschieben kann eine Ausnahme auslösen (z. B. kann die Zuweisung während des Kopierens fehlschlagen), wohingegen ein Verweis auf einen vorhandenen Wert dies nicht kann.

Wenn Sie tun eine Kopie benötigen, ist die Übergabe und Rückgabe nach Wert normalerweise (immer?) Die beste Option. Tatsächlich würde ich mich in C++ 03 im Allgemeinen nicht darum kümmern, es sei denn, Sie stellen fest, dass zusätzliche Kopien tatsächlich ein Leistungsproblem verursachen. Copy Elision scheint auf modernen Compilern ziemlich zuverlässig zu sein. Ich denke, dass die Skepsis und das Bestehen der Leute, dass Sie Ihre Tabelle der Compiler-Unterstützung für RVO überprüfen müssen, heutzutage größtenteils überholt sind.


Kurz gesagt, C++ 11 ändert in dieser Hinsicht nichts, außer für Leute, die Copy Elision nicht vertrauen.

43
bames53

Fast.

In C++ 17 haben wir basic_string_view<?>, womit wir uns auf einen engen Anwendungsfall für std::string const& -Parameter beschränken.

Die Existenz der Verschiebungssemantik hat einen Anwendungsfall für std::string const& beseitigt - wenn Sie vorhaben, den Parameter zu speichern, ist es optimaler, einen std::string nach Wert zu nehmen, da Sie move aus der Parameter.

Wenn jemand Ihre Funktion mit einem rohen C "string" aufruft, bedeutet dies, dass immer nur ein std::string -Puffer zugewiesen wird, im Gegensatz zu zwei im Fall std::string const&.

Wenn Sie jedoch keine Kopie erstellen möchten, ist das Aufnehmen mit std::string const& in C++ 14 weiterhin hilfreich.

Mit std::string_view können Sie die Funktionalität von '\0' effizienter nutzen, ohne die API zu gefährden, die Zeichenpuffer im C-Stil std::string erwartet Zuweisung. Ein roher C-String kann sogar ohne Zuweisung oder Kopieren von Zeichen in einen std::string_view umgewandelt werden.

Zu diesem Zeitpunkt wird std::string const& verwendet, wenn Sie den Datengroßhandel nicht kopieren und ihn an eine C-API weiterleiten, die einen nullterminierten Puffer erwartet, und wenn Sie die Zeichenfolge höherer Ebene benötigen Funktionen, die std::string bereitstellt. In der Praxis ist dies ein seltener Satz von Anforderungen.

std::string ist nicht Plain Old Data (POD) , und seine unformatierte Größe ist nicht die relevanteste Sache überhaupt. Wenn Sie beispielsweise eine Zeichenfolge übergeben, die über der Länge von SSO liegt und auf dem Heap zugeordnet ist, würde ich erwarten, dass der Kopierkonstruktor den SSO-Speicher nicht kopiert.

Der Grund, warum dies empfohlen wird, ist, dass inval aus dem Argumentausdruck erstellt wird und daher immer entsprechend verschoben oder kopiert wird. Es tritt kein Leistungsverlust auf, vorausgesetzt, Sie benötigen den Besitz des Arguments. Andernfalls ist eine const Referenz möglicherweise immer noch der bessere Weg.

16
Puppy

Ich habe die Antwort von diese Frage hier kopiert/eingefügt und die Namen und die Schreibweise so geändert, dass sie zu dieser Frage passen.

Hier ist Code, um zu messen, was gefragt wird:

#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());
}

Für mich ergibt das:

$ 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&&)

Die folgende Tabelle fasst meine Ergebnisse zusammen (mit clang -std = c ++ 11). Die erste Zahl ist die Anzahl der Kopierkonstruktionen und die zweite Zahl ist die Anzahl der Verschiebungskonstruktionen:

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

Die Pass-by-Value-Lösung erfordert nur eine Überladung, kostet jedoch eine zusätzliche Verschiebungskonstruktion, wenn l- und x-Werte übergeben werden. Dies kann für eine gegebene Situation akzeptabel oder nicht akzeptabel sein. Beide Lösungen haben Vor- und Nachteile.

16
Howard Hinnant

Herb Sutter ist zusammen mit Bjarne Stroustroup nach wie vor bekannt, als er const std::string& als Parametertyp empfiehlt. Siehe https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rf-in .

In keiner der anderen Antworten wird eine Fallstricke erwähnt: Wenn Sie ein String-Literal an einen const std::string& -Parameter übergeben, wird ein Verweis auf einen temporären String übergeben, der im laufenden Betrieb erstellt wird, um die Zeichen von aufzunehmen das wörtliche. Wenn Sie diese Referenz dann speichern, wird sie ungültig, sobald die Zuordnung der temporären Zeichenfolge aufgehoben wurde. Um sicherzugehen, müssen Sie eine Kopie speichern, nicht die Referenz. Das Problem rührt von der Tatsache her, dass Zeichenfolgenliterale vom Typ const char[N] sind und eine Heraufstufung auf std::string erfordern.

Der folgende Code veranschaulicht die Fallstricke und die Problemumgehung sowie eine geringfügige Effizienzoption - Überladen mit einer const char* -Methode, wie unter Gibt es eine Möglichkeit, ein Zeichenfolgenliteral als Referenz in C++ zu übergeben beschrieben =.

(Hinweis: Sutter & Stroustroup empfehlen, dass Sie, wenn Sie eine Kopie des Strings behalten, auch eine überladene Funktion mit einem && -Parameter und std :: move () bereitstellen.)

#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
}

AUSGABE:

const char * constructor
const std::string& constructor

Second string
Second string
13
circlepi314

IMO unter Verwendung der C++ - Referenz für std::string ist eine schnelle und kurze lokale Optimierung, während die Verwendung der Wertübergabe eine bessere globale Optimierung sein könnte (oder nicht).

Die Antwort lautet also: es kommt auf die Umstände an:

  1. Wenn Sie den gesamten Code von außen nach innen schreiben, wissen Sie, was der Code tut, können Sie den Verweis const std::string & verwenden.
  2. Wenn Sie den Bibliothekscode schreiben oder viel Bibliothekscode verwenden, in dem Zeichenfolgen übergeben werden, gewinnen Sie wahrscheinlich mehr an globaler Bedeutung, wenn Sie dem Verhalten des std::string copy -Konstruktors vertrauen.
7

Siehe "Herb Sutter" Zurück zu den Grundlagen! Grundlagen des modernen C++ - Stils . Unter anderem geht er auf die in der Vergangenheit gegebenen Ratschläge zum Übergeben von Parametern und neue Ideen ein, die mit C + einhergehen +11 und befasst sich speziell mit der Idee, Zeichenfolgen nach Wert zu übergeben.

slide 24

Die Benchmarks zeigen, dass das Übergeben von std::strings nach Wert in Fällen, in denen die Funktion es trotzdem kopiert, erheblich langsamer sein kann!

Dies liegt daran, dass Sie erzwingen, immer eine vollständige Kopie zu erstellen (und dann an die richtige Stelle zu verschieben), während die const& -Version die alte Zeichenfolge aktualisiert, die möglicherweise den bereits zugewiesenen Puffer wiederverwendet.

Siehe seine Folie 27: Für „Set“ -Funktionen ist Option 1 dieselbe wie immer. Option 2 fügt eine Überladung für die R-Wert-Referenz hinzu, aber dies führt zu einer kombinatorischen Explosion, wenn mehrere Parameter vorhanden sind.

Nur für "sink" -Parameter, bei denen eine Zeichenfolge erstellt werden muss (deren vorhandener Wert nicht geändert werden darf), ist der Trick der Wertübergabe gültig. Das heißt, Konstruktoren , bei denen der Parameter das Element des übereinstimmenden Typs direkt initialisiert.

Wenn du sehen willst, wie tief du dich damit beschäftigen kannst, schau dir Nicolai Josuttis Präsentation an und viel Glück damit ( „Perfekt - Fertig!“ n mal nach bemängelung mit der vorigen version. schon mal da gewesen?)


Dies ist in den Standardrichtlinien auch als ⧺F.15 zusammengefasst.

5
JDługosz

Wie @ JDługosz in den Kommentaren ausführt, gibt Herb in einem weiteren (späteren?) Vortrag einen anderen Rat: https://youtu.be/xnqTKD8uD64?t=54m50s .

Sein Rat läuft darauf hinaus, nur Wertparameter für eine Funktion f zu verwenden, die sogenannte Senkenargumente verwendet, vorausgesetzt, Sie verschieben das Konstrukt aus diesen Senkenargumenten.

Dieser allgemeine Ansatz fügt nur den Overhead eines Verschiebungskonstruktors für lvalue- und rvalue-Argumente hinzu, verglichen mit einer optimalen Implementierung von f, die auf lvalue- bzw. rvalue-Argumente zugeschnitten ist. Angenommen, f verwendet einen Werteparameter, wobei T ein konstruierbarer Typ zum Kopieren und Verschieben ist:

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

Das Aufrufen von f mit einem lvalue-Argument führt dazu, dass ein Kopierkonstruktor aufgerufen wird, um x zu konstruieren, und ein Verschiebungskonstruktor, um y zu konstruieren. Wenn Sie dagegen f mit einem rvalue-Argument aufrufen, wird ein move-Konstruktor aufgerufen, um x zu erstellen, und ein anderer move-Konstruktor, um y zu erstellen.

Im Allgemeinen lautet die optimale Implementierung von f für lvalue-Argumente wie folgt:

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

In diesem Fall wird nur ein Kopienkonstruktor aufgerufen, um y zu konstruieren. Die optimale Implementierung von f für rvalue-Argumente lautet wiederum im Allgemeinen wie folgt:

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

In diesem Fall wird nur ein Verschiebungskonstruktor aufgerufen, um y zu konstruieren.

Ein sinnvoller Kompromiss ist es also, einen Werteparameter zu verwenden und einen zusätzlichen Verschiebungskonstruktor zu veranlassen, der entweder lvalue- oder rvalue-Argumente für die optimale Implementierung fordert, was auch der Ratschlag in Herbs Vortrag ist.

Wie @ JDługosz in den Kommentaren ausführte, ist die Übergabe von Werten nur für Funktionen sinnvoll, die ein Objekt aus dem Argument sink erstellen. Wenn Sie über eine Funktion f verfügen, die das Argument kopiert, ist der Aufwand für den Ansatz der Übergabe von Werten höher als für den allgemeinen Ansatz der Übergabe von Konstanten. Der Pass-by-Value-Ansatz für eine Funktion f, die eine Kopie ihres Parameters beibehält, hat die Form:

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

In diesem Fall gibt es eine Kopierkonstruktion und eine Verschiebungszuweisung für ein Wertargument sowie eine Verschiebungskonstruktion und eine Verschiebungszuweisung für ein Wertargument. Der optimalste Fall für ein lvalue-Argument ist:

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

Dies führt nur zu einer Zuweisung, die potenziell viel billiger ist als der Kopierkonstruktor plus die für den Pass-by-Value-Ansatz erforderliche Verschiebungszuweisung. Der Grund dafür ist, dass die Zuweisung möglicherweise vorhandenen zugewiesenen Speicher in y wiederverwendet und daher Zuweisungen verhindert (de), wohingegen der Kopierkonstruktor normalerweise Speicher zuweist.

Für ein rvalue-Argument hat die optimalste Implementierung für f, die eine Kopie beibehält, die folgende Form:

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

Also nur ein Zugauftrag in diesem Fall. Die Übergabe eines Werts an die Version von f, die eine konstante Referenz verwendet, kostet nur eine Zuweisung anstelle einer Verschiebungszuweisung. Relativ gesehen ist die Version von f vorzuziehen, die in diesem Fall eine konstante Referenz als allgemeine Implementierung verwendet.

Im Allgemeinen müssen Sie für eine optimale Implementierung eine Überladung oder eine perfekte Weiterleitung durchführen, wie im Vortrag gezeigt. Der Nachteil ist eine kombinatorische Explosion der Anzahl der erforderlichen Überladungen in Abhängigkeit von der Anzahl der Parameter für f, falls Sie sich für eine Überladung der Wertkategorie des Arguments entscheiden. Perfekte Weiterleitung hat den Nachteil, dass f zu einer Vorlagenfunktion wird, die verhindert, dass sie virtuell wird, und zu einem wesentlich komplexeren Code führt, wenn Sie sie zu 100% richtig machen möchten (Einzelheiten finden Sie im Vortrag).

2

Das Problem ist, dass "const" ein nicht granulares Qualifikationsmerkmal ist. Was normalerweise mit "const string ref" gemeint ist, ist "Diesen String nicht ändern", nicht "Den Referenzzähler nicht ändern". In C++ gibt es einfach keine Möglichkeit zu sagen, dass welche -Mitglieder "const" sind. Sie sind entweder alle oder keiner von ihnen.

Um dieses Sprachproblem zu umgehen, lässt STL könnte "C ()" in Ihrem Beispiel eine bewegungssemantische Kopie erstellen trotzdem und ignoriert pflichtbewusst die "const" in Bezug auf die Referenzzahl (veränderlich). Solange es gut spezifiziert war, würde dies in Ordnung sein.

Da STL dies nicht tut, habe ich eine Version eines Strings, der den Referenzzähler von const_casts <> entfernt (keine Möglichkeit, etwas in einer Klassenhierarchie rückwirkend veränderbar zu machen). Und machen Sie den ganzen Tag lang Kopien von ihnen in tiefen Funktionen, ohne Undichtigkeiten oder Probleme.

Da C++ hier keine "abgeleitete Klassenkonstanten-Granularität" bietet, ist es die beste Lösung, eine gute Spezifikation zu schreiben und ein glänzendes neues "const portable string" (cmstring) -Objekt zu erstellen, die ich gesehen habe.

1
Erik Aronesty