2009-04-08 11 views
53

C++ Referenzen haben zwei Eigenschaften:Warum sind Referenzen reseatable in nicht C++

  • immer auf das gleiche Objekt zeigen Sie.
  • Sie nicht 0.

Pointers sind das Gegenteil sein:

  • Sie auf verschiedene Objekte verweisen.
  • Sie können 0.

sein Warum gibt es keine "Nicht-NULL-Werte zulässt, reseatable Verweis oder Zeiger" in C++? Ich kann mir keinen guten Grund vorstellen, warum Referenzen nicht umsetzbar sein sollten.

Edit: Die Frage kommt oft, weil ich in der Regel Referenzen verwenden, wenn ich sicherstellen möchten, dass eine „Vereinigung“ (Ich bin die Worte „Referenz“ oder „Zeiger“ hier zu vermeiden) nie ungültig ist.

Ich glaube nicht, dass ich jemals dachte "großartig, dass dieser Verweis immer auf das gleiche Objekt bezieht". Wenn Referenzen reseatable wären, könnte man immer noch das aktuelle Verhalten wie diese:

int i = 3; 
int& const j = i;

Dies ist bereits rechtlich C++, aber sinnlos.

Hiermit erneuere ich meine Frage wie folgt aus: „Was ist die Logik hinter der war‚ein Referenz ist das Objekt‘Design Warum wurde es für zweckmäßig erachtet, Referenzen zu haben immer das gleiche Objekt, statt nur von? wenn es als const erklärt wird? "

Cheers, Felix

+2

Ich weiß, das ist ein bisschen spät, aber Michael Burrs Antwort scheint die vollständigste zu sein. Es erklärt Stroustrups philosophische Designentscheidungen. Alle anderen scheinen das Argument zu erbitten (in einer Runde herum). – jww

Antwort

72

Der Grund, dass C++ nicht Sie Referenzen erlaubt erneut zu binden ist in Stroustrup „Entwurf und Entwicklung von C++“ gegeben:

Es ist nicht möglich zu ändern, worauf sich eine Referenz nach der Initialisierung bezieht. Das heißt, sobald eine C++ - Referenz initialisiert ist, kann nicht später auf ein anderes Objekt Bezug genommen werden; es kann nicht erneut gebunden werden. Ich wurde in der Vergangenheit von Algol68 Referenzen gebissen, wo r1=r2 entweder r1 auf das Objekt zugewiesen zuweisen oder einen neuen Referenzwert zu r1 (wieder bindend r1) je nach Art von r2 zuweisen kann. Ich wollte solche Probleme in C++ vermeiden.

2

C++ Referenzen können manchmal gezwungen werden, seine mit einigen Compilern (es ist nur eine schlechte Idee, so * zu tun, und es verstößt gegen den Standard *).

int &x = *((int*)0); // Illegal but some compilers accept it 

EDIT: verschiedene Menschen nach, die den Standard viel besser als ich wissen, erzeugt der obige Code „nicht definiertes Verhalten“. In mindestens einigen Versionen von GCC und Visual Studio habe ich gesehen, dass dies die erwartete Sache ist: das Äquivalent zum Setzen eines Zeigers auf NULL (und verursacht eine NULL-Zeigerausnahme beim Zugriff).

+0

"Es ist nur eine schlechte Idee": weil der Nutzen eines solchen Lebewesens, äh, begrenzt ist. – dmckee

+0

Es kann jedoch versehentlich auftreten, also ist es gut, von der Möglichkeit zu wissen. Ich habe schon mal darüber gesprochen. –

+0

Dieser Code ist illegal: In gültigem C++ - Code darf dies niemals passieren. Also nein, Referenzen können NICHT 0 sein, wie vom C++ - Standard garantiert. –

31

In C++ wird oft gesagt, dass "die Referenz ist das Objekt". In gewissem Sinne ist es richtig: Obwohl Referenzen beim Kompilieren des Quellcodes als Zeiger behandelt werden, soll die Referenz ein Objekt bezeichnen, das beim Aufruf einer Funktion nicht kopiert wird. Da Referenzen nicht direkt adressierbar sind (z. B. Referenzen haben keine Adresse, & gibt die Adresse des Objekts zurück), wäre es nicht sinnvoll, sie neu zuzuweisen. Darüber hinaus hat C++ bereits Zeiger, die die Semantik der Neueinstellung behandeln.

+0

Ich bekomme immer noch nicht wirklich den Grund, warum die (meiner Meinung nach) nützliche nicht-wiedersetzbare Referenz nicht in C++ ist. Die aufgeworfenen Probleme scheinen nicht schwer zu lösen zu sein, und ich denke, dass die Programmzuverlässigkeit zunehmen würde. Annahme dieser Antwort wegen der Stimmen. – TheFogger

+1

Korrektur: "obwohl Referenzen _oft_ als Zeiger behandelt werden" –

+9

"Die Referenz * ist * das Objekt" ist falsch. Außer für den Spezialfall der Lebensdauerverlängerung eines Temporären ist die Lebensdauer einer Referenz verschieden von der Lebensdauer des Objekts, auf das Bezug genommen wird.Wenn die Referenz tatsächlich das Objekt wäre, würde das Objekt zerstört werden, wenn die Referenz den Gültigkeitsbereich verlassen würde (oder umgekehrt so lange leben würde, wie es einen Bezug darauf hätte), und dies ist nicht der Fall. –

0

Ich würde mir vorstellen, dass es um die Optimierung geht.

Statische Optimierung ist viel einfacher, wenn Sie eindeutig wissen können, welches Bit Speicher eine Variable bedeutet. Zeiger brechen diese Bedingung und re-setable Referenz würde auch.

4

Es wäre wahrscheinlich weniger verwirrend gewesen, C++ Referenzen "Aliase" zu nennen? Wie andere bereits erwähnt haben, sollten Referenzen in C++ von als die Variable sein, auf die sie sich beziehen, nicht als Zeiger/ Referenz auf die Variable. Als solcher kann ich mir keinen guten Grund vorstellen, sie sollten rücksetzbar sein.

Wenn es um Zeiger geht, ist es oft sinnvoll, null als Wert zuzulassen (und ansonsten wahrscheinlich eine Referenz). Wenn Sie speziell null nicht zulassen wollen, zu halten, können Sie immer Ihre eigene Smart-Pointer-Typ-Code;)

+0

+1 für die intelligente Zeiger-Idee; Ich hätte das Gleiche gesagt. Neue Features werden für die Sprache nicht berücksichtigt, wenn es mit dem bestehenden Standard bereits möglich ist. –

0

Weil manchmal Dinge nicht re-pointable sein sollten. (Z. B. der Verweis auf einen Singleton.)

Weil es in einer Funktion großartig ist zu wissen, dass Ihr Argument nicht null sein kann.

Aber meistens, weil es erlaubt, etwas zu haben, das wirklich ein Zeiger ist, aber der wie ein lokales Wertobjekt handelt. C++ versucht hart, Stroustrup zu zitieren, um Klasseninstanzen als "ints d" zu machen. Das Übergeben eines int by vau ist billig, weil ein int in ein Maschinenregister passt. Klassen sind oft größer als Ints, und ihre Weitergabe nach Wert hat einen erheblichen Overhead.

Da es möglich ist, einen Zeiger (der oft die Größe eines int oder zwei ints hat) zu übergeben, der wie ein Wertobjekt aussieht, können wir saubereren Code schreiben, ohne das "Implementierungsdetail" der Dereferenzierung. Und zusammen mit dem Überladen von Operatoren erlaubt es uns, Klassen zu schreiben, die Syntax ähnlich der Syntax verwenden, die mit Ints verwendet wird. Insbesondere erlaubt es uns, Template-Klassen mit einer Syntax zu schreiben, die gleichermaßen auf primitive wie Ints und Klassen (wie eine komplexe Zahlenklasse) angewendet werden kann.

Und vor allem beim Überladen von Operatoren gibt es Orte, an denen wir ein Objekt zurückgeben sollten, aber es ist viel billiger, einen Zeiger zurückzugeben. Oncve wieder, eine Referenz ist unsere "out.

Und Zeiger sind hart. Nicht für Sie, vielleicht, und nicht für jeden, der einen Zeiger realisiert, ist nur der Wert einer Speicheradresse. Aber unter Hinweis auf meine CS 101-Klasse, sie stolperten eine Anzahl von Studenten.

kann verwirrend sein.

Heck, nach 40 Jahren C, können die Menschen noch nicht einmal zustimmen, wenn ein Zeiger Erklärung sein sollte:

char* p; 

oder

char *p; 
+0

Der richtige Weg ist: char * p. Die Meinungsverschiedenheit betrifft den Stil, der subjektiv ist. – GManNickG

+1

@GMan: Sie haben einen Tippfehler dort. Sie sagten, dass der korrekte Weg "char * p" ist, wo es offensichtlich "char * p" ist, da der Typ pointer-to-char ist. Nur ein hilfreicher, ähm, Zeiger. :) –

+0

@John: * Leider *, C und C++ Syntax Regeln brechen Deklarationen in zwei Teile. Um zu veranschaulichen: "char * p, c;" deklariert 1 pointer-to-char mit dem Namen p und ein Zeichen (NOT pointer-to-char) mit dem Namen c. Ich weiß, ja. :/ –

15

Denn dann würden Sie keine reseatable Art haben Das kann nicht 0 sein. Es sei denn, Sie haben 3 Arten von Referenzen/Zeigern eingeschlossen. Was würde die Sprache nur für sehr wenig Gewinn verkomplizieren (Und dann warum nicht auch den vierten Typ hinzufügen? Nicht umsetzbare Referenz, die 0 sein kann?)

Eine bessere Frage könnte sein, warum sollten Sie Referenzen wiedersetzbar sein? Wenn sie es wären, wären sie in vielen Situationen weniger nützlich. Es würde es dem Compiler schwerer machen, eine Alias-Analyse durchzuführen.

Es scheint, dass der Hauptgrund, auf den in Java oder C# Referenzen verwiesen werden, weil sie die Arbeit von Zeigern erledigen. Sie zeigen auf Objekte. Sie sind keine Aliasnamen für ein Objekt.

Was sollte der folgende Effekt haben?

In C++ heute, mit nicht umsetzbaren Referenzen, ist es einfach. j ist ein Alias ​​für i, und ich lande mit dem Wert 43.

Wenn Referenzen wiedersetzbar waren, dann würde die dritte Zeile die Referenz j an einen anderen Wert binden. Es würde nicht mehr Alias ​​i, sondern das Integer-Literal 43 (was natürlich nicht gültig ist). Oder vielleicht ein einfaches (oder zumindest syntaktisch gültig) Beispiel:

int i = 42; 
int k = 43; 
int& j = i; 
j = k; 

Mit reseatable Referenzen. j würde nach Auswertung dieses Codes auf k zeigen. Mit den nicht umsetzbaren Referenzen von C++ zeigt j immer noch auf i, und i wird der Wert 43 zugewiesen.

Durch die Angabe referenzieller Referenzen wird die Semantik der Sprache geändert. Die Referenz kann nicht länger ein Alias ​​für eine andere Variable sein. Stattdessen wird es zu einem separaten Werttyp mit einem eigenen Zuweisungsoperator. Und dann wäre eine der häufigsten Verwendungen von Referenzen unmöglich. Und im Gegenzug würde nichts gewonnen werden. Die neu gewonnene Funktionalität für Referenzen existierte bereits in Form von Zeigern. Jetzt hätten wir zwei Möglichkeiten, dasselbe zu tun und keine Möglichkeit, die Referenzen in der aktuellen C++ - Sprache zu tun.

+1

+1. Der entscheidende Teil dort ist "die neu gewonnene Funktionalität für Referenzen existierte bereits in Form von Zeigern." –

+6

Dort * sind * schon 3 Typen. Der möglicherweise null-nicht umsetzbare Typ ist "int * const". Aus Konsistenzgründen sollte es 4 geben, da möglicherweise-null und wiedersetzbar ziemlich orthogonal sind. – MSalters

+2

Ja, sie sind orthogonal, aber die Sprache muss nicht jede Kombination bereitstellen. Die Frage ist, würde die zusätzliche Ausdruckskraft die zusätzliche Komplexität überwiegen? C++ ist mehr als komplex genug, also brauchen sie einen besseren Grund, ein Feature hinzuzufügen als "es existiert noch nicht". – jalf

4

Eine Referenz ist kein Zeiger, sie kann als Zeiger im Hintergrund implementiert werden, aber ihr Kernkonzept ist nicht äquivalent zu einem Zeiger. Eine Referenz sollte wie es *is* das Objekt, auf das es sich bezieht, betrachtet werden. Daher können Sie es nicht ändern, und es kann nicht NULL sein.

Ein Zeiger ist einfach eine Variable, die eine Speicheradresse enthält. Der Zeiger selbst hat eine eigene Speicheradresse, und innerhalb dieser Speicheradresse hält er eine andere Speicheradresse, auf die er zeigen soll. Eine Referenz ist nicht die gleiche, sie hat keine eigene Adresse und kann daher nicht geändert werden, um eine andere Adresse zu "halten".

ich denke, die parashift C++ FAQ on references sagt es am besten:

Wichtiger Hinweis: Auch wenn eine Referenz oft eine Adresse umgesetzt wird in der zugrunde liegenden Baugruppe Sprache verwenden, denken Sie bitte nicht von einer Referenz als ein lustig aussehender Zeiger auf ein Objekt. Eine Referenz ist das Objekt . Es ist kein Zeiger auf das Objekt oder eine Kopie des Objekts. Es ist das Objekt .

und wieder in FAQ 8.5:

Im Gegensatz zu einem Zeiger, sobald eine Referenz an ein Objekt gebunden ist, ist es nicht "reseated" auf ein anderes Objekt werden kann. Die Referenz selbst ist kein Objekt (es hat keine Identität; unter der Adresse eine Referenz gibt Ihnen die Adresse der Referent; denken Sie daran: die Referenz ist ihr Referent).

5

Eine wiedersetzbare Referenz wäre funktionell identisch mit einem Zeiger.

In Bezug auf NULL-Kompatibilität: Sie können nicht garantieren, dass eine solche "umsetzbare Referenz" zur Kompilierungszeit nicht NULL ist, so dass ein solcher Test zur Laufzeit stattfinden müsste. Man könnte dies erreichen, sich durch eine Smart-Pointer-Stil Klassenvorlage zu schreiben, die eine Ausnahme auslöst, wenn initialisiert oder NULL zugewiesen:

struct null_pointer_exception { ... }; 

template<typename T> 
struct non_null_pointer { 
    // No default ctor as it could only sensibly produce a NULL pointer 
    non_null_pointer(T* p) : _p(p) { die_if_null(); } 
    non_null_pointer(non_null_pointer const& nnp) : _p(nnp._p) {} 
    non_null_pointer& operator=(T* p) { _p = p; die_if_null(); } 
    non_null_pointer& operator=(non_null_pointer const& nnp) { _p = nnp._p; } 

    T& operator*() { return *_p; } 
    T const& operator*() const { return *_p; } 
    T* operator->() { return _p; } 

    // Allow implicit conversion to T* for convenience 
    operator T*() const { return _p; } 

    // You also need to implement operators for +, -, +=, -=, ++, -- 

private: 
    T* _p; 
    void die_if_null() const { 
     if (!_p) { throw null_pointer_exception(); } 
    } 
}; 

Dies könnte gelegentlich nützlich sein - eine Funktion, um eine non_null_pointer<int> Parameter unter kommuniziert sicherlich mehr Informationen der Anrufer als eine Funktion unter int*.

+4

FALSCH. Sie können trivial garantieren, dass eine umsetzbare Referenz nicht null ist. Bei der erneuten Erstellung würden Sie auch einen lvalue verwenden, genau wie bei der Initialisierung. Z.B. int foo [0] = {0}; int ref = foo [0]; ref = & = foo [1]/* Setzt ref, nicht foo [0] */ – MSalters

+1

gute Idee tut es mit dem Lvalue. Haha. –

+1

@MSalters: Nein, die Angabe von lvalues ​​garantiert nicht, dass die umsetzbare Referenz nicht null ist, genauso wenig wie die Initialisierung einer regulären Referenz - siehe 8.3.2.4. Zum Beispiel: "int & r = * static_cast (0);". Es gibt eine Welt des Unterschieds zwischen "x ist verboten" und "x erzeugt undefiniertes Verhalten". –

1

Sie können dies nicht tun:

int theInt = 0; 
int& refToTheInt = theInt; 

int otherInt = 42; 
refToTheInt = otherInt; 

... aus dem gleichen Grund, warum secondInt und firstInt den gleichen Wert nicht hier haben:

int firstInt = 1; 
int secondInt = 2; 
secondInt = firstInt; 
firstInt = 3; 

assert(firstInt != secondInt); 
0

ich sie immer gefragt, warum keinen Referenzzuweisungsoperator (sagen wir: =) dafür gemacht.

Um jemandem auf die Nerven zu gehen, habe ich Code geschrieben, um das Ziel einer Referenz in einer Struktur zu ändern.

Nein, ich empfehle nicht, meinen Trick zu wiederholen. Es bricht, wenn es auf eine ausreichend andere Architektur portiert wird.

0

Sein halb ernst: IMHO sie wenig anders als Zeiger zu machen;) Sie wissen, dass Sie schreiben können:

MyClass & c = *new MyClass(); 

Wenn auch Sie könnte später schreiben:

c = *new MyClass("other") 

würde es machen Sinn, irgendwelche Hinweise neben Zeigern zu haben?

0

Die Tatsache, dass Referenzen in C++ nicht nullfähig sind, ist ein Nebeneffekt davon, dass sie nur ein Alias ​​sind.

1

Dies ist nicht wirklich eine Antwort, aber eine Abhilfe für diese Einschränkung.

Wenn Sie versuchen, eine Referenz "erneut zu binden", versuchen Sie tatsächlich, denselben Namen zu verwenden, um im folgenden Kontext auf einen neuen Wert zu verweisen. In C++ kann dies durch Einführung eines Blockumfangs erreicht werden.

In JALF dem Beispiel

int i = 42; 
int k = 43; 
int& j = i; 
//change i, or change j? 
j = k; 

, wenn Sie i ändern wollen, schreiben Sie es wie oben. Wenn Sie jedoch die Bedeutung von j bedeuten k ändern möchten, können Sie dies tun:

int i = 42; 
int k = 43; 
int& j = i; 
//change i, or change j? 
//change j! 
{ 
    int& j = k; 
    //do what ever with j's new meaning 
} 
2

Intrestingly, hier viele Antworten ein wenig unscharf sind oder sogar nebensächlich (zB es ist nicht, weil Referenzen nicht Null sein kann oder ähnlich, in der Tat können Sie leicht ein Beispiel konstruieren, wo eine Referenz Null ist).

Der wirkliche Grund, warum das Zurücksetzen einer Referenz nicht möglich ist, ist ziemlich einfach.

  • Pointers ermöglichen es Ihnen, zwei Dinge zu tun: Um den Wert hinter dem Zeiger zu ändern (entweder durch den -> oder * Operator) und den Zeiger selbst zu ändern (direkte assign =). Beispiel:

    int a; 
    int * p = &a;
    1. den Wert ändern, muss dereferencing: *p = 42;
    2. Ändern der Zeiger: p = 0;
  • Referenzen können Sie nur auf den Wert ändern. Warum? Da gibt es keine andere Syntax, um die Zurücksetzung auszudrücken. Beispiel:

    int a = 10; 
    int b = 20; 
    int & r = a; 
    r = b; // re-set r to b, or set a to 20?

Mit anderen Worten, es wäre nicht eindeutig sein, wenn Sie einen Verweis auf neu eingestellt durften. Es macht noch mehr Sinn, wenn sie durch Bezugnahme vorbei:

void foo(int & r) 
{ 
    int b = 20; 
    r = b; // re-set r to a? or set a to 20? 
} 
void main() 
{ 
    int a = 10; 
    foo(a); 
} 

Hoffnung, die :-)

0

hilft es eine Abhilfe ist, wenn Sie eine Membervariable wollen, der eine Referenz ist und Sie wollen es erneut zu binden können. Obwohl ich es nützlich und zuverlässig finde, beachte, dass es einige (sehr schwache) Annahmen zum Speicherlayout verwendet. Es liegt an Ihnen zu entscheiden, ob es innerhalb Ihrer Codierungsstandards liegt.

#include <iostream> 

struct Field_a_t 
{ 
    int& a_; 
    Field_a_t(int& a) 
     : a_(a) {} 
    Field_a_t& operator=(int& a) 
    { 
     // a_.~int(); // do this if you have a non-trivial destructor 
     new(this)Field_a_t(a); 
    } 
}; 

struct MyType : Field_a_t 
{ 
    char c_; 
    MyType(int& a, char c) 
     : Field_a_t(a) 
     , c_(c) {} 
}; 

int main() 
{ 
    int i = 1; 
    int j = 2; 
    MyType x(i, 'x'); 
    std::cout << x.a_; 
    x.a_ = 3; 
    std::cout << i; 
    ((Field_a_t&)x) = j; 
    std::cout << x.a_; 
    x.a_ = 4; 
    std::cout << j; 
} 

Dies ist nicht sehr effizient, da Sie für jedes neu zuzuweisende Referenzfeld einen separaten Typ benötigen und diese zu Basisklassen machen müssen; Außerdem gibt es hier eine schwache Annahme, dass eine Klasse mit einem einzelnen Referenztyp kein __vfptr oder ein anderes type_id-bezogenes Feld hat, das Laufzeitbindungen von MyType möglicherweise zerstören könnte. Alle Compiler, die ich kenne, erfüllen diese Bedingung (und es würde wenig Sinn machen, dies nicht zu tun).