2009-02-26 11 views
2

Ich habe daran gearbeitet, rohe Zeiger durch referenzierte Zeiger zu ersetzen, die nur eine const-Version des zugrunde liegenden zeigen. Mein Ziel ist es, die Speichernutzung (und die unnötige Zeit, in der ich komplexe Objekte konstruiere und zerstöre) zu reduzieren, ohne mich in eine Situation zu begeben, in der Code Zugriff auf Speicher hat, den er nicht besitzt. Ich kenne das Zirkelreferenzproblem bei der Referenzzählung, aber mein Code sollte niemals eine solche Situation erzeugen.Smart Pointer mit "this" in C++

Die Anforderung von const-ness funktioniert, weil ich ein System verwende, in dem Klassen im Allgemeinen keine nichtkonstanten Elemente bereitstellen, und anstatt ein Objekt zu ändern, müssen Sie eine Methode aufrufen, die ein neues Objekt zurückgibt, das aus der Änderung resultiert. Dies ist wahrscheinlich ein Design-Muster, aber ich weiß es nicht namentlich.

Mein Problem ist gekommen, wenn ich eine Methode habe, die Zeiger auf ein Objekt seines Typs zurückgibt, das sich manchmal selbst ist. Bisher sah es in etwa so aus:

Ich kann keinen guten Weg sehen, diese Rückkehr ein intelligenter Zeiger. Der naive Ansatz würde etwas wie return CRcPtr<Foo>(this) sein, aber das bricht die Besitz-Semantik, weil das, was ich zurückgebe und wer früher das Objekt besaß, nun beide denkt, dass sie Besitz haben, aber nicht voneinander wissen. Die einzige sichere Sache zu tun wäre return CRcPtr<Foo>(new Foo(*this)), aber das vereitelt meine Absicht, unnötigen Speicherverbrauch zu beschränken.

Gibt es eine Möglichkeit, einen intelligenten Zeiger sicher zurückzugeben, ohne zusätzlichen Speicher zuzuweisen? Mein Verdacht ist, dass es nicht ist. Wenn es welche gäbe, wie würde es funktionieren, wenn das Objekt auf dem Stapel zugewiesen worden wäre? This question scheint verwandt zu sein, ist aber nicht das Gleiche, weil er seine Funktionen einfach dazu bringen kann, rohe Zeiger als Parameter zu nehmen und weil er die Boost-Bibliothek benutzt.

Als Referenz ist meine home-rolled Smart-Pointer-Implementierung unten. Ich bin sicher, dass es robuster sein könnte, aber es ist portabler als abhängig von Boost oder tr1, die überall verfügbar sind, und bis zu diesem Problem hat es für mich gut funktioniert.

template <class T> 
class CRcPtr 
{ 
public: 
    explicit CRcPtr(T * p_pBaldPtr) 
    { 
    m_pInternal = p_pBaldPtr; 
    m_iCount = new unsigned short(1); 
    } 

    CRcPtr(const CRcPtr & p_Other) 
    { Acquire(p_Other); } 

    template <class U> 
    explicit CRcPtr(const CRcPtr<U> & p_It) 
    { 
    m_pInternal = dynamic_cast< T * >(p_It.m_pInternal); 
    if(m_pInternal) 
    { 
     m_iCount = p_It.m_iCount; 
     (*m_iCount)++; 
    } 
    else 
     m_iCount = new unsigned short(1); 
    } 

    ~CRcPtr() 
    { Release(); } 

    CRcPtr & operator=(const CRcPtr & p_Other) 
    { 
    Release(); 
    Acquire(p_Other); 
    } 

    const T & operator*() const 
    { return *m_pInternal; } 

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

    const T * get() const 
    { return m_pInternal; } 

private: 
    void Release() 
    { 
    (*m_iCount)--; 
    if(*m_iCount == 0) 
    { 
     delete m_pInternal; 
     delete m_iCount; 
     m_pInternal = 0; 
     m_iCount = 0; 
    } 
    } 

    void Acquire(const CRcPtr & p_Other) 
    { 
    m_pInternal = p_Other.m_pInternal; 
    m_iCount = p_Other.m_iCount; 
    (*m_iCount)++; 
    } 

    template <class U> 
    friend class CRcPtr; 

    T * m_pInternal; 
    unsigned short * m_iCount; 
}; 

template <class U, class T> 
CRcPtr<U> ref_cast(const CRcPtr<T> & p_It) 
{ return CRcPtr<U>(p_It); } 

Bearbeiten: Vielen Dank für die Antworten. Ich hatte gehofft, Boost oder tr1 zu vermeiden, aber ich weiß, dass es nicht unklug ist, nicht gut getestete Bibliotheken zu verwenden. Ich bin mir ziemlich sicher, dass das, was ich implementiert habe, nicht ähnlich wie std :: auto_ptr ist, aber eher tr1 :: shared_ptr ähnelt, außer dass es nur eine const Version des internen Zeigers exponiert und einige der Features im offiziellen fehlt Ausführung. Ich möchte wirklich ein aufdringliches Schema wie das von Gian Paolo vorgeschlagene vermeiden. Ich bin mir bewusst, dass meine Implementierung nicht threadsicher ist, aber dies ist eine Single-Thread-Anwendung.

+0

Warum haben Sie einen Zeiger für m_iCount verwendet? Das ist eine Verschwendung von Speicher ... – SoapBox

+0

Was wäre die Alternative? Jede Kopie muss in der Lage sein, denselben Zähler zu inkrementieren und zu dekrementieren, so dass ich keinen Weg sehen kann, dass nicht alle einen Zeiger darauf haben. –

Antwort

5

Werfen Sie einen Blick auf die Quelle für Boost shared pointers. Wenn Sie eine Klasse von enable_shared_from_this<T> ableiten, können Sie die Elementfunktion shared_from_this() aufrufen, um so etwas zu tun.

+0

+1. Ich habe boost :: shared_ptr und shared_from_this vorher benutzt und es funktioniert sehr gut –

0

Was die Semantik betrifft, ist das Tolle an der Unveränderlichkeit, dass Sie Daten sicher zwischen Objekten und Threads austauschen können und sich nicht darum kümmern müssen, dass Menschen Werte ändern, auf die Sie angewiesen sind. Sie sollten sich nicht um die Eigentumsrechte kümmern müssen, solange Ihr Refcounting-Schema korrekt ist (ich habe Ihr nicht gelesen, aber Boost trotzdem verwendet).

0

Wie von Ferruccio, benötigen Sie eine Form von shared Zeiger. Dh, eine, die (sicher!) Zwischen mehreren Objekten geteilt werden kann. Eine Möglichkeit, dies zu erreichen, besteht darin, alle Ihre Objekte (zumindest diejenigen, die für den gemeinsamen Zeiger bestimmt sind) von einer Klasse abzuleiten, die die tatsächliche Referenzanzahl implementiert.Auf diese Weise trägt der eigentliche Zeiger die aktuelle Zählung mit sich, und Sie teilen den Zeiger zwischen so vielen verschiedenen Objekten, wie Sie möchten.

Was Sie zur Zeit haben ist sehr ähnlich zu std :: auto_ptr und Sie stoßen auf die gleichen Probleme mit der Eigentümerschaft. Google es und Sie sollten einige nützliche Informationen finden. Beachten Sie, dass es zusätzliche Komplikationen bei geteilten Zeigern gibt: Insbesondere in Umgebungen mit mehreren Threads muss die Referenzzählung atomar sein, und die Selbstzuweisung muss gehandhabt werden, um eine Erhöhung der internen Zählung zu vermeiden, sodass das Objekt niemals zerstört wird.

Wieder ist Google dein Freund hier. Suchen Sie einfach nach Informationen über geteilte Zeiger in C++ und Sie werden Tonnen von Informationen finden.