Standard-Semantik für Pass-by-Reference in C ++ [geschlossen]

7

EDIT: Diese Frage bezieht sich mehr auf das Sprachen-Engineering als C ++ selbst. Ich habe C ++ als Beispiel benutzt, um zu zeigen, was ich wollte, hauptsächlich weil ich es täglich benutze. Ich wollte nicht wissen, wie es in C ++ funktioniert, sondern eine Diskussion darüber eröffnen, wie es gemacht werden könnte

So funktioniert es jetzt nicht, das ist die Art und Weise, wie ich wünschen könnte es gemacht werden, und das würde die C Kompatibilität definitiv brechen, aber das ist, was ich extern "C" denke .

Ich meine, in jeder Funktion oder Methode, die Sie gerade deklarieren, müssen Sie explizit schreiben, dass das Objekt per Referenz vor dem Referenzoperator gesendet wird. Ich wünsche mir, dass jeder Nicht-POD-Typ automatisch per Referenz gesendet wird, weil ich das oft verwende, eigentlich für jedes Objekt, das mehr als 32 Bits groß ist, und das ist fast jede Klasse von mir.

Lassen Sie uns ein Beispiel dafür geben, wie es jetzt ist: Angenommen, a , b und c sind Klassen:

%Vor%

Was ich mir jetzt wünsche:

%Vor%

Nun könnte sich do_nothing_on_c () genauso verhalten wie heute.

Das wäre zumindest für mich interessant, fühlt sich viel klarer an und auch wenn Sie wissen jeder Nicht-POD-Parameter durch Bezugnahme kommt, glaube ich, dass die Fehler die gleichen wären wie bei Ihnen um es explizit zu deklarieren.

Ein anderer Gesichtspunkt für diese Änderung, von jemandem, der aus C kommt, der Referenzoperator scheint mir eine Möglichkeit zu sein, die Variable Adresse zu bekommen, das ist die Art, wie ich Zeiger benutzt habe. Ich meine, es ist der gleiche Operator, aber mit unterschiedlicher Semantik in verschiedenen Kontexten, fühlt sich das nicht auch ein bisschen falsch für dich?

    
Edwin Jarvis 28.09.2008, 17:13
quelle

9 Antworten

15

Ich denke, du verpasst den Punkt von C ++ und C ++ - Semantik. Du hast die Tatsache übersehen, dass C ++ korrekt ist, wenn man (fast) alles nach Wert übergibt, weil es so ist, wie es in C gemacht wird. Immer . Aber nicht nur in C, wie ich dir unten zeigen werde ...

Parameter Semantik auf C

In C wird alles nach Wert übergeben. "Primitive" und "PODs" werden übergeben, indem ihr Wert kopiert wird. Ändern Sie sie in Ihrer Funktion, und das Original wird nicht geändert. Dennoch könnten die Kosten für das Kopieren einiger PODs nicht-trivial sein.

Wenn Sie die Zeiger-Notation (das *) verwenden, werden Sie nicht als Referenz übergeben. Sie übergeben eine Kopie der Adresse. Das ist mehr oder weniger das Gleiche, mit nur einem feinen Unterschied:

%Vor%

Der endgültige Wert von p- & gt; -Wert ist 32. Weil p übergeben wurde, indem der Wert der Adresse kopiert wurde. Also wurde das ursprüngliche p nicht verändert (und das neue wurde durchgesickert).

Parameter Semantik auf Java und C Sharp

Es kann für einige überraschend sein, aber in Java wird auch alles nach Wert kopiert. Das obige C-Beispiel würde in Java genau die gleichen Ergebnisse liefern. Das ist fast das, was Sie wollen, aber Sie wären nicht in der Lage, primitive "by reference / pointer" so einfach zu übergeben wie in C.

In C # wurde das Schlüsselwort "ref" hinzugefügt. Es funktioniert mehr oder weniger wie die Referenz in C ++. Der Punkt ist, dass Sie in C # sowohl bei der Funktionsdeklaration als auch bei jedem Aufruf angeben müssen. Ich denke, das ist nicht das, was du willst, wieder.

Parameter Semantik zu C ++

In C ++ wird fast alles durch Kopieren des Werts übergeben. Wenn Sie nur den Typ des Symbols verwenden, kopieren Sie das Symbol (wie in C). Wenn Sie also das * verwenden, übergeben Sie eine Kopie der Adresse des Symbols.

Aber wenn Sie das & amp; am verwenden, dann nehmen Sie an, dass Sie das reale Objekt übergeben (sei es struct, int, pointer, was auch immer): Die Referenz.

Es ist leicht, es als Syntaxzucker zu verwechseln (d. h. hinter den Kulissen funktioniert es wie ein Zeiger und der generierte Code ist derselbe, der für einen Zeiger verwendet wird). Aber ...

Die Wahrheit ist, dass die Referenz mehr als nur Syntaxzucker ist.

  • Im Gegensatz zu Zeigern autorisiert es das Bearbeiten des Objekts wie auf einem Stapel.
  • Unline-Zeiger, wenn sie mit dem Schlüsselwort const verbunden sind, erlaubt sie die implizite Beförderung von einem Typ zu einem anderen (hauptsächlich über Konstruktoren).
  • Im Gegensatz zu Zeigern soll das Symbol nicht NULL / ungültig sein.
  • Anders als bei der "Kopie" verbringen Sie keine unnötige Zeit damit, das Objekt zu kopieren
  • Im Gegensatz zu "by-copy" können Sie es als [out] -Parameter
  • verwenden
  • Im Gegensatz zu "by-copy" können Sie den gesamten OOP-Bereich in C ++ verwenden (d. h. Sie übergeben ein vollständiges Objekt an eine Funktion, die auf eine Schnittstelle wartet).

Also, Referenzen haben das Beste aus beiden Welten.

Sehen wir uns das C-Beispiel an, aber mit einer C ++ - Variante der doSomethingElse-Funktion:

%Vor%

Das Ergebnis ist 42, und das alte p wurde durchgesickert und durch das neue p ersetzt. Weil wir im Gegensatz zum C-Code keine Kopie des Zeigers übergeben, sondern den Verweis auf den Zeiger, also den Zeiger selbst.

Wenn Sie mit C ++ arbeiten, muss das obige Beispiel cristal clear sein. Wenn nicht, dann fehlt etwas.

Fazit

C ++ ist pass-by-copy / value, weil es so ist, wie alles funktioniert, sei es in C, in C # oder in Java (sogar in JavaScript ... :-p ...). Und wie C # hat C ++ einen Referenzoperator / ein Schlüsselwort, als Bonus .

Nun, soweit ich es verstehe, tun Sie vielleicht, was ich halb jockend C + nenne, also C mit einigen eingeschränkten C ++ - Funktionen.

Vielleicht verwenden Sie typedefs (es wird Ihre C ++ Kollegen verärgern, aber den Code durch unbrauchbare Typedefs zu verschmutzen ...), aber dies wird nur die Tatsache verdecken, dass Sie C ++ wirklich vermissen.

Wie in einem anderen Beitrag gesagt, sollten Sie Ihre Denkweise von C-Entwicklung (von was auch immer) zu C ++ - Entwicklung ändern, oder Sie sollten vielleicht in eine andere Sprache wechseln. Aber programmieren Sie nicht den C-Weg mit C ++ - Funktionen, denn wenn Sie die Macht der verwendeten Idiome bewusst ignorieren / verschleiern, erzeugen Sie einen suboptimalen Code.

Hinweis: Und übergeben Sie nichts anderes als primitive Kopien. Sie kastrieren Ihre Funktion aus ihrer OO-Kapazität, und in C ++ ist dies nicht das, was Sie wollen.

Bearbeiten

Die Frage wurde etwas geändert (siehe Ссылка ). Ich lasse meine ursprüngliche Antwort und beantworte die folgenden neuen Fragen.

Was halten Sie von einer Standard-Semantik für Pass-by-Reference in C ++? Wie Sie bereits gesagt haben, würde dies die Kompatibilität beeinträchtigen, und Sie haben unterschiedliche Vorübersetzungen für Primitive (dh eingebaute Typen) , die immer noch von copy übergeben würden) und structs / objects (die als Referenzen übergeben würden). Du müsstest einen weiteren Operator hinzufügen, um "pass-by-value" zu bedeuten (das externe "C" ist ziemlich schrecklich und wird bereits für etwas anderes verwendet).Nein, ich mag es wirklich, wie es heute in C ++ gemacht wird.

[...] Der Referenzoperator scheint mir eine Möglichkeit zu sein, die Variablenadresse zu bekommen, das ist die Art, wie ich Zeiger verwendet habe. Ich meine, es ist derselbe Operator, aber mit unterschiedlicher Semantik in verschiedenen Kontexten, fühlt sich das nicht auch ein bisschen falsch für dich? Ja und nein. Betreiber & gt; & gt; änderte seine Semantik, wenn sie auch mit C ++ - Streams verwendet wurde. Dann können Sie den Operator + = verwenden, um strcat zu ersetzen. Ich schätze, der Betreiber & amp; wurde wegen seiner Bedeutung als "Gegenteil von Zeiger" verwendet, und weil sie kein weiteres Symbol verwenden wollten (ASCII ist begrenzt, und der Bereichsoperator :: sowie Zeiger - & gt; zeigt, dass wenige andere Symbole verwendbar sind). Aber jetzt, wenn & amp; stört dich, & amp; & amp; wird Sie wirklich entnerven, da sie eine unäre & amp; & amp; in C ++ 0x (eine Art Superreferenz ...). Ich muss es noch selbst verdauen ...

    
paercebal 28.09.2008, 18:41
quelle
10

Eine Compileroption, die die Bedeutung eines Codeabschnitts völlig verändert, klingt für mich nach einer wirklich schlechten Idee. Entweder benutze die C ++ Syntax oder finde eine andere Sprache.

    
Andy Brice 28.09.2008 17:31
quelle
2

Ich möchte Referenzen nicht mehr missbrauchen, indem ich jeden (nicht qualifizierten) Parameter zu einer Referenz mache.

Der Hauptgrund, warum Referenzen zu C ++ hinzugefügt wurden, war , um das Überladen von Operatoren zu unterstützen ; Wenn Sie Semantik "Pass-by-Reference" wollen, hatte C einen vollkommen vernünftigen Weg: Zeiger.

Die Verwendung von Zeigern macht deutlich, dass Sie den Wert des spitzen Objekts ändern möchten, und Sie können dies sehen, indem Sie einfach auf den Funktionsaufruf schauen. Sie müssen sich die Funktionsdeklaration nicht anschauen, um zu sehen, ob sie a verwendet Referenz.

Siehe auch

  

Ich möchte das Argument ändern,   Soll ich einen Zeiger verwenden oder soll ich ihn verwenden   eine Referenz? Ich kenne kein starkes   logischer Grund. Wenn nicht '   Objekt '' (z. B. ein Nullzeiger) ist   akzeptabel, mit einem Zeiger machen   Sinn. Mein persönlicher Stil ist es, ein   Zeiger, wenn ich einen ändern möchte   Objekt, weil in einigen Kontexten   macht es leichter zu erkennen, dass a   Modifikation ist möglich.

aus den gleichen FAQ .

    
aib 28.09.2008 17:45
quelle
1

Ja, ich bin der Meinung, dass das eine ziemlich verwirrende Überlastung ist.

Dies ist, was Microsoft über die Situation zu sagen hat:

  

Verwechseln Sie keine Referenzdeklarationen mit der Verwendung des Address-of-Operators. Wenn & amp; Vor dem Bezeichner steht ein Typ wie int oder char, und der Bezeichner wird als Verweis auf den Typ deklariert. Wenn & amp; Bezeichner ist kein Typ vorangestellt, die Verwendung ist die des Operators Adresse.

Ich bin nicht wirklich gut in C oder C ++, aber ich bekomme größere Kopfschmerzen, wenn ich die verschiedenen Anwendungen von * und & amp; auf beiden Sprachen als ich in Assembler codieren.

    
Ryan 28.09.2008 17:21
quelle
1

Der beste Rat ist, sich daran zu gewöhnen, was Sie wirklich wollen. Wenn Sie keinen Kopierkonstruktor haben (oder ihn nicht verwenden wollen), ist es schön, wenn Sie Referenzen verwenden. Bei großen Objekten ist das billiger. Jedoch werden dann Mutationen zu dem Parameter außerhalb der Klasse gefühlt. Sie könnten stattdessen an const reference übergeben - dann gibt es keine Mutationen, aber Sie können keine lokalen Änderungen vornehmen. Übergeben Sie const by-value für billige Objekte, die in der Funktion schreibgeschützt sein sollen, und übergeben Sie einen nicht konstanten Wert, wenn Sie eine Kopie haben möchten, an der Sie lokale Änderungen vornehmen können.

Jede Permutation (by-value / by-reference und const / non-const) hat wichtige Unterschiede, die definitiv nicht äquivalent sind.

    
Pat Notz 28.09.2008 17:34
quelle
1

Wenn Sie den Wert übergeben, kopieren Sie Daten in den Stapel. Für den Fall, dass Sie einen Operator = für die Struktur oder Klasse definiert haben, die Sie übergeben, wird er ausgeführt. Es gibt keine Compiler-Richtlinie, von der ich weiß, dass sie das Dilemma der impliziten Sprachverwirrung wegwaschen würde, die die vorgeschlagene Änderung inhärent verursachen würde.

Eine bewährte Methode besteht darin, Werte durch eine konstante Referenz zu übergeben, nicht nur als Referenz. Dies stellt sicher, dass der Wert in der aufrufenden Funktion nicht geändert werden kann. Dies ist ein Element einer const-korrekten Codebasis.

Eine vollständig const-correct Codebase geht noch weiter und fügt const bis zum Ende der Prototypen hinzu. Überlegen Sie:

%Vor%

Wenn Sie ein Foo-Objekt an eine Funktion mit dem Präfix const übergeben, können Sie PrintStats () aufrufen. Der Compiler würde bei einem Aufruf von ChangeStats () fehlschlagen.

%Vor%     
Michael Labbé 28.09.2008 17:42
quelle
1

Ich glaube ehrlich, dass diese ganze Weitergabe von Referenzwerten in C ++ irreführend ist. Alles wird als Wert übergeben. Sie haben drei Fälle:

  1. Übergeben Sie eine lokale Kopie einer Variablen

    %Vor%
  2. Übergeben Sie eine lokale Kopie eines Zeigers an eine Variable

    %Vor%
  3. Wo Sie eine 'Referenz' übergeben, aber durch Compiler-Magie ist es so, als ob Sie einen Zeiger übergeben und ihn jedes Mal einfach dereferenziert hätten.

    %Vor%

Ich persönlich finde es viel weniger verwirrend, sich daran zu erinnern, dass alles wert ist. In einigen Fällen ist dieser Wert möglicherweise eine Adresse, mit der Sie Dinge außerhalb der Funktion ändern können.

Was Sie vorschlagen, würde dem Programmierer nicht erlauben, Nummer 1 zu tun. Ich persönlich denke, dass es eine schlechte Idee wäre, diese Option wegzunehmen. Ein wichtiges Plus von C / C ++ ist eine feinkörnige Speicherverwaltung. Wenn man alles als Referenz durchläuft, versucht man einfach, C ++ mehr wie Java zu machen.

    
user7545 28.09.2008 17:20
quelle
0

da ist etwas nicht klar. wenn du sagst:

  

int b (b & amp; param);

Was hast du für das zweite 'b' vor? Hast du vergessen einen Typ einzuführen? Hast du vergessen, in Bezug auf das erste 'b' anders zu schreiben? denkst du nicht, es ist klar, etwas zu schreiben wie:

%Vor%

Seit jetzt nehme ich an, dass Sie meinen, was ich schreibe.

Nun, Ihre Frage lautet: "Glauben Sie nicht, dass es besser ist, dass der Compiler jeden Wert eines Nicht-POD als pass-by-ref berücksichtigt?". Das erste Problem ist, dass es Ihren Vertrag bricht. Ich nehme an, Sie meinen Pass-by-CONST-Referenz, und nicht nur durch Bezugnahme.

Ihre Frage ist nun auf diese Frage reduziert: "Wissen Sie, ob es eine Compiler-Direktive gibt, die den Funktionsaufruf nach Wert optimieren kann?"

Die Antwort lautet jetzt "Ich weiß es nicht".

    
ugasoft 28.09.2008 18:36
quelle
0

Ich denke, dass C ++ sehr unordentlich wird, wenn Sie anfangen, alle verfügbaren Parameter mit ihren const-Variationen zu mischen.

Es wird schnell außer Kontrolle geraten, alle Aufrufe des Kopierkonstruktors zu verfolgen, alle Dereferenzierungen überladen und so weiter.

    
Lorenzo Boccaccia 29.09.2008 19:35
quelle