2010-04-08 6 views
14

Ich habe Code, der wie folgt aussieht:C++ konstante Bezug Lebensdauer (Behälteradapter)

class T {}; 

class container { 
const T &first, T &second; 
container(const T&first, const T & second); 
}; 

class adapter : T {}; 

container(adapter(), adapter()); 

Ich dachte Lebensdauer von konstanter Referenz Lebensdauer des Behälters sein würde. Es sieht jedoch anders aus, das Adapterobjekt wird nach dem Erstellen des Containers zerstört, und die Referenz bleibt hängen.

Was ist die korrekte Lebensdauer?

ist der Stapelbereich des temporären Adapterobjekts der Bereich des Containerobjekts oder des Containerkonstruktors?

Wie wird das temporäre Bindeobjekt korrekt in die Klassenmemberreferenz implementiert?

Dank

Antwort

16

Gemäß dem C++ 03-Standard hat eine temporäre Bindung an eine Referenz je nach Kontext unterschiedliche Lebensdauern. In Ihrem Beispiel, ich glaube, der markierte Abschnitt unterhalb gilt (12.2/5 „Temporäre Objekte“):

Das temporäre, an dem die Referenz gebunden ist oder die temporäre, die zu einem Subobjekt von denen die vorübergehenden das komplette Objekt ist ist gebunden für die Lebensdauer der Referenz, außer wie unten angegeben. Eine temporäre Bindung an ein Referenzelement im Konstruktor ctor-initializer (12.6.2) bleibt bestehen, bis der Konstruktor beendet wird. Eine temporäre Bindung an einen Referenzparameter in einem Funktionsaufruf (5.2.2) besteht bis zum Abschluss des vollständigen Ausdrucks, der den Aufruf enthält.

So, während eine temporäre Bindung ist eine fortschrittliche Technik, um die Lebensdauer des temporären Objekts (GotW #88: A Candidate For the "Most Important const") zu verlängern, wird es offenbar nicht, dass Sie in diesem Fall helfen. Auf der anderen Seite hat Eric Niebler einen interessanten Artikel, in dem eine interessante (wenn auch verworrene) Technik besprochen wird, mit der die Konstruktoren Ihrer Klasse ableiten können, ob ein temporäres Objekt (eigentlich ein rvalue) an sie übergeben wurde (und daher würde kopiert haben) oder einen nicht nur vorübergehenden (L-Wert) als bestanden (und damit möglicherweise hätte sicher eine Referenz gebunkert anstelle des Kopierens):

Gut Glück damit - jedes Mal, wenn ich den Artikel lese, ich Ich muss alles durcharbeiten, als hätte ich das Material noch nie gesehen. Es bleibt nur für einen flüchtigen Moment bei mir ...

Und ich sollte erwähnen, dass C++ 0x rvalue Referenzen Nieblers Techniken unnötig machen sollte. Rvalue-Referenzen werden von MSVC 2010 unterstützt, das voraussichtlich in einer Woche veröffentlicht wird (am 12. April 2010, wenn ich mich richtig erinnere). Ich weiß nicht, was der Status von Rvalue-Referenzen in GCC ist.

+0

Ich denke, in diesem Fall ist das temporäre an einen Funktionsaufruf-Parameter (den Aufruf des Konstruktors) wie im nächsten Satz gebunden. Ja, es ist wegen des Aliasing im ctor-Initialisierer auch an das Member gebunden, und ja, es wird bestehen bleiben, bis der Konstruktor beendet wird (länger, wenn der Full-Ausdruck, der den Konstruktoraufruf enthält, auch andere Dinge tut). Aber ich denke, die hervorgehobene Passage bezieht sich auf Dinge wie 'struct container {const & adapter a; container(): a (Adapter()) {}}; '. –

+0

@Steve: Bei näherem Hinsehen denke ich, du hast recht - ich werde die Antwort aktualisieren (das gleiche Ergebnis aber). –

6

Temporäre const Referenzen haben nur die Lebensdauer der aktuellen Anweisung (das heißt, sie gehen nur den Gültigkeitsbereich vor dem Semikolon). Die Faustregel ist also niemals auf eine const-Referenz angewiesen, die über die Lebensdauer der Funktion hinaus existiert, die sie als Parameter empfängt, in diesem Fall ist das nur der Konstruktor. Sobald der Konstruktor fertig ist, verlassen Sie sich nicht auf irgendwelche Const-Referenzen, die noch vorhanden sind.

Es gibt keine Möglichkeit, diese Lebensdauer für Provisorien zu ändern/zu überschreiben/zu verlängern. Wenn Sie eine längere Lebensdauer wollen, ein tatsächliches Objekt verwenden und keine vorübergehendes:

adapter a, b; 
container(a, b); // lifetime is the lifetime of a and b 

Oder noch besser, einfach nicht verwendet ständige Verweise auf den Teilnehmer, außer in den schlimmsten Fällen, in denen die Objekte sehr eng miteinander verbunden sind und definitiv nicht vorübergehend.

+2

genauer zu sein, sie bis zum Ende des vollen Ausdruck leben sie erstellt wurden – GManNickG

+1

„Es gibt keine Möglichkeit zu ändern/Betätigung/verlängern diese Lebensdauer für Provisorien.“ - eigentlich ist da, es ist einfach nicht nützlich in Fällen wie diesem. Wenn Sie eine temporäre Konstante verwenden, um eine Konstante const mit der automatischen Dauer zu initialisieren, wird die Lebensdauer des temporären Objekts verlängert, bis der Bereich des automatischen Objekts beendet wird. –

1

Die Referenz existiert für die gesamte Lebensdauer von container, aber das Objekt, auf das verwiesen wird, existiert nur für die Lebensdauer dieses Objekts. In diesem Fall haben Sie Ihre Referenz an ein temporäres Objekt mit automatischer Speicherzuweisung gebunden ("Stapelzuordnung", wenn Sie so wollen, obwohl dies keine C++ - Nomenklatur ist). Daher können Sie nicht erwarten, dass das Temporäre jenseits der Anweisung existiert, in der es geschrieben wurde (da es unmittelbar nach dem Aufruf des Konstruktors für container aus dem Gültigkeitsbereich verschwindet). Der beste Weg, um damit umzugehen, ist eine Kopie anstelle einer Referenz zu verwenden. Da Sie sowieso eine Konstante const verwenden, wird es eine ähnliche Semantik haben.

Sie sollten Ihre Klasse neu definieren als:

 
template<typename T> 
class container 
{ 
    public: 
     container(const T& first, const T& second) : first(first), second(second) {} 
    private: 
     const T first; 
     const T second; 
}; 

Alternativ können Sie Ihre Objekte einen Namen, damit sie nicht gehen out of scope geben könnte:

 
    adaptor first; 
    adaptor second; 
    container c(first,second); 

Aber ich glaube nicht, Dies ist eine gute Idee, da eine Aussage wie return c ungültig ist.

bearbeiten
Wenn Ihr Ziel ist es, Objekte zu teilen, um die Kosten für das Kopieren zu vermeiden, sollten Sie Smart-Pointer-Objekte betrachten verwenden.Zum Beispiel können wir Ihr Objekt mit Smart-Pointer neu zu definieren, wie folgt:

 
template<typename T> 
class container 
{ 
    public: 
     container(const boost::shared_ptr<const T>& first, const boost::shared_ptr<const T>& second) : first(first), second(second) {} 
    private: 
     boost::shared_ptr<const T> first; 
     boost::shared_ptr<const T> second; 
}; 

Sie können dann mit:

 
boost::shared_ptr<const adaptor> first(new adaptor); 
boost::shared_ptr<const adaptor> second(new adaptor); 
container<adaptor> c(first,second); 

Oder, wenn Sie lokal veränderlichem Kopie der ersten und zweiten haben wollen:

 
boost::shared_ptr<adaptor> first(new adaptor); 
boost::shared_ptr<adaptor> second(new adaptor); 
container<adaptor> c(boost::const_pointer_cast<const adaptor>(first),boost::const_pointer_cast<const adaptor>(second)); 
+0

die realen Objekte sind ziemlich schwer mit Nebenwirkungen Konstruktoren. Ich versuche, das Kopieren zu vermeiden. – Anycorn

+2

@aaa, in diesem Fall sollten Sie Smart Pointer wie boost :: shared_ptr verwenden. –

+0

Ich dachte daran, aber Klasse ist in der öffentlichen Schnittstelle, die versuchen, Boost frei zu halten – Anycorn

-1

Tun Sie dies nicht. Ein temporäres Objekt wird unmittelbar nach dem Ausdruck gelöscht, in dem es erstellt wurde (außer in dem Fall, dass es unmittelbar an eine Referenz gebunden ist; in diesem Fall ist es der Bereich der Referenz). Die Lebensdauer kann nicht auf die der Klasse erweitert werden.

Deshalb speichere ich nie Mitglieder als Referenzen - nur kopierte Objekte oder Zeiger. Für mich machen Pointer deutlich, dass das Leben ins Spiel kommt. Besonders im Fall eines Konstruktors ist es nicht offensichtlich, dass Ihre Konstruktorparameter die Klasse selbst überleben müssen.

+1

-1: Zeiger sollten wenn möglich mit Referenzen ersetzt werden. –

+0

Ich habe nicht -1, aber sie leben bis zum Ende des vollständigen Ausdrucks, in dem sie erstellt wurden, nicht der Bereich. – GManNickG

+0

Zunächst ist das eine lächerliche Aussage. Zweitens machen Referenzen in diesem Fall dieses Verhalten völlig unansehnlich. Lahme -1. – Stephen

0

Wenn Sie nicht kopieren wollen, dann nehme ich an, dass der Container die gespeicherten Instanzen selbst erstellen muss.

Wenn Sie den Standardkonstruktor aufrufen möchten, sollte es kein Problem sein. Rufen Sie einfach den Standardkonstruktor von Container auf.

Es ist wahrscheinlich problematischer, wenn Sie einen nicht standardmäßigen Konstruktor des enthaltenen Typs aufrufen möchten. C++ 0x wird dafür bessere Lösungen haben.

Als Übung kann der Container ein T akzeptieren, oder ein Objekt, das die Argumente für den Konstruktor von T enthält. Dies beruht immer noch auf RVO (Rückgabewert-Optimierung).

template <class T1> 
class construct_with_1 
{ 
    T1 _1; 
public: 
    construct_with_1(const T1& t1): _1(t1) {} 
    template <class U> 
    U construct() const { return U(_1); } 
}; 

template <class T1, class T2> 
class construct_with_2 
{ 
    T1 _1; 
    T2 _2; 
public: 
    construct_with_2(const T1& t1, const T2& t2): _1(t1), _2(t2) {} 
    template <class U> 
    U construct() const { return U(_1, _2); } 
}; 

//etc for other arities 

template <class T1> 
construct_with_1<T1> construct_with(const T1& t1) 
{ 
    return construct_with_1<T1>(t1); 
} 

template <class T1, class T2> 
construct_with_2<T1, T2> construct_with(const T1& t1, const T2& t2) 
{ 
    return construct_with_2<T1, T2>(t1, t2); 
} 

//etc 
template <class T> 
T construct(const T& source) { return source; } 

template <class T, class T1> 
T construct(const construct_with_1<T1>& args) 
{ 
    return args.template construct<T>(); 
} 

template <class T, class T1, class T2> 
T construct(const construct_with_2<T1, T2>& args) 
{ 
    return args.template construct<T>(); 
} 

template <class T> 
class Container 
{ 
public: 
    T first, second; 

    template <class T1, class T2> 
    Container(const T1& a = T1(), const T2& b = T2()) : 
     first(construct<T>(a)), second(construct<T>(b)) {} 
}; 

#include <iostream> 

class Test 
{ 
    int n; 
    double d; 
public: 
    Test(int a, double b = 0.0): n(a), d(b) { std::cout << "Test(" << a << ", " << b << ")\n"; } 
    Test(const Test& x): n(x.n), d(x.d) { std::cout << "Test(const Test&)\n"; } 
    void foo() const { std::cout << "Test.foo(" << n << ", " << d << ")\n"; } 
}; 

int main() 
{ 
    Test test(4, 3.14); 
    Container<Test> a(construct_with(1), test); //first constructed internally, second copied 
    a.first.foo(); 
    a.second.foo(); 
}