2010-04-12 2 views
6

Ich habe mehrere Mitglieder in meiner Klasse, die const sind und daher nur wie so über die initialiser Liste initialisiert werden:zulassen Mitglied const, während immer noch Betreiber unterstützt werden = von der Klasse

class MyItemT 
{ 
public: 
    MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo) 
     : mMyPacket(aMyPacket), 
      mMyInfo(aMyInfo) 
    { 
    } 

private: 
    const MyPacketT mMyPacket; 
    const MyInfoT mMyInfo; 
}; 

Meine Klasse sein kann in einigen unserer intern definierten Containerklassen (z. B. Vektoren) verwendet, und diese Container erfordern, dass operator= in der Klasse definiert ist.

Natürlich, mein operator= braucht so etwas zu tun:

MyItemT& 
MyItemT::operator=(const MyItemT& other) 
{ 
    mMyPacket = other.mPacket; 
    mMyInfo = other.mMyInfo; 
    return *this; 
} 

was natürlich nicht funktioniert, weil mMyPacket und mMyInfo sind const Mitglieder.

Anders als diese Mitglieder nicht const machen (was ich nicht tun möchte), irgendwelche Ideen, wie ich das beheben könnte?

+8

Wenn Ihre Klasse zuweisbar ist und diese Mitglieder geändert werden können, nehmen Sie die 'const' weg. Es macht einfach keinen Sinn. – GManNickG

+0

@GMan - Ich denke du hast Recht. Ich suchte nach einem Freund-ähnlichen Ansatz, der es erlaubt, dass 'operator =' spezielle Privilegien hat, die verhindern, dass die Mitglieder manuell überschrieben werden. Aber nachdem ich gelesen habe, was @MichaelMozek gesagt hat, ist das überhaupt nicht sicher. – LeopardSkinPillBoxHat

Antwort

7

Sie verletzen die Definition von const, wenn Sie einen Zuweisungsoperator haben, der sie nach Abschluss der Konstruktion ändern kann. Wenn Sie wirklich brauchen, denke ich Potatoswatter 's Platzierung neue Methode ist wahrscheinlich am besten, aber wenn Sie einen Zuweisungsoperator haben, sind Ihre Variablen nicht wirklich const, da jemand könnte nur eine neue Instanz erstellen und verwenden Sie ihre Werte zu ändern

+0

@Michael - True, und ein gültiger Punkt. – LeopardSkinPillBoxHat

+1

Auf der anderen Seite hindert nichts den Benutzer daran, solche Böswilligkeiten manuell auszuführen. (Oder benutzen Sie einfach 'const_cast'!) Natürlich dokumentieren Sie, dass' operator = 'Verweise auf Member ungültig macht. – Potatoswatter

+1

Nun, zwischen 'const_cast' und dem' mutable'-Spezifizierer kann man das Konzept der const-Variablen ziemlich gründlich zerstören :). Es gibt wenig C/C++ wird Sie tatsächlich davon abhalten, wenn Sie es zwingen, –

2

Es ist ein schmutziger Hack, aber man kann sie zerstören und rekonstruieren:

MyItemT& 
MyItemT::operator=(const MyItemT& other) 
{ 
    if (this == &other) return *this; // "suggested" by Herb Sutter ;v) 

    this->MyItemT::~MyItemT(); 
    try { 
     new(this) MyItemT(other); 
    } catch (...) { 
     new(this) MyItemT(); // nothrow 
     throw; 
    } 
    return *this; 
} 

Edit: damit ich meine Glaubwürdigkeit zu zerstören, ich weiß nicht wirklich, diese selbst tun, würde ich die const entfernen. Ich habe jedoch darüber diskutiert, die Praxis zu ändern, denn const ist einfach nützlich und besser zu verwenden, wo immer es möglich ist.

Manchmal wird zwischen der Ressource und dem von einem Objekt dargestellten Wert unterschieden. Ein Member kann durch Wertänderungen konstant sein, solange die Ressource gleich ist, und es wäre schön, die Kompilierungssicherheit dafür zu bekommen.

Bearbeiten 2: @Charles Bailey hat diesen wunderbaren (und sehr kritischen) Link zur Verfügung gestellt: http://gotw.ca/gotw/023.htm.

  • Semantik ist in jeder abgeleiteten Klasse schwierig operator=.
  • Es kann ineffizient sein, da es Zuweisungsoperatoren nicht aufruft, die definiert worden sind.
  • Es mit wonky operator& Überlastungen unvereinbar ist (was auch immer)
  • usw.

bearbeiten 3: die „welche Ressource“ vs „welchen Wert“ Unterscheidung Denken durch, so scheint es klar, dass operator= sollte immer ändern der Wert und nicht die Ressource. Die Ressourcen-ID kann dann const sein. In dem Beispiel sind alle Mitglieder const. Wenn die "Info" ist, was in dem "Paket" gespeichert ist, dann sollte das Paket möglicherweise const sein und die Information nicht.

Das Problem ist also nicht so sehr herauszufinden, die Semantik der Zuweisung als Mangel an einem offensichtlichen Wert in diesem Beispiel, wenn die "Info" tatsächlich Metadaten ist. Wenn die Klasse MyItemT von einem Paket zu einem anderen wechseln will, muss sie entweder aufgeben und stattdessen auto_ptr<MyItemT> verwenden oder zu einem ähnlichen Hack wie oben (der Identitätstest ist nicht erforderlich, aber catch übrig) implementiert von außerhalb.Aber operator= sollte nicht die Ressourcenbindung ändern, außer als eine besondere Funktion, die absolut nichts anderes stören wird.

Beachten Sie, dass diese Konvention gut zu Sutters Empfehlung passt, die Konstruktion von Kopien in Bezug auf die Zuweisung zu implementieren.

+2

@Potatoswatter - Danke für den Vorschlag, aber ich denke, das ist wahrscheinlich dreckiger, als die Mitglieder nicht-const zu machen. – LeopardSkinPillBoxHat

+0

@Leopard: verstanden, aber auf der anderen Seite gibt es hier nichts wirklich unsicheres. In gewisser Weise erhalten Sie eine zusätzliche Garantie, dass kein veralteter Zustand verwendet wird. – Potatoswatter

+3

Wenn der Kopierkonstruktor auswirft, könnten Sie verloren sein, denn, so weit die Sprache es geht, wird '* das 'unvermeidlich wieder zerstört. – UncleBens

3

Anstatt Objekte direkt in Ihren Containern zu speichern, können Sie möglicherweise Zeiger (oder Smartpointer) speichern. Auf diese Weise müssen Sie keines der Mitglieder Ihrer Klasse mutieren - Sie erhalten exakt das gleiche Objekt, das Sie übergeben haben, const und alle.

Natürlich wird dies wahrscheinlich die Speicherverwaltung Ihrer Anwendung etwas ändern, was ein guter Grund sein kann, nicht zu wollen.

+0

@Andrew - Interessanter Vorschlag, aber in meinem Fall wahrscheinlich nicht praktikabel. Meine Konstruktorargumente sind stack allocated objects, daher muss ich eine Kopie von ihnen in meine Klasse aufnehmen, da es sich um lebenslange Probleme handelt (da meine Klasse die Stack-Objekte überleben wird). – LeopardSkinPillBoxHat

+0

@LeopardSkinPillBoxHat: Selbst wenn die Klasse Zeiger verwaltet, bedeutet das nicht, dass Sie lebenslange Probleme haben - die Klasse würde diese Daten trotzdem in ihre eigenen Zuordnungen kopieren, es wäre nur, dass sie "neu" vom Stapel mit sind Zeiger (oder Smartpointer) auf diese dynamisch zugewiesenen Bits, anstatt die Kopien direkt in dem Objekt zu haben. –

1

Sie könnten in Betracht ziehen, die MyPacketT und MyInfoT Mitglieder Zeiger auf const (oder intelligente Zeiger auf const) zu machen. Auf diese Weise werden die Daten selbst immer noch als const und unveränderlich markiert, aber Sie können sauber zu einer anderen Menge von const Daten in einer Zuweisung wechseln, wenn das sinnvoll ist. In der Tat können Sie das Swap-Idiom verwenden, um die Zuweisung in einer sicheren Ausnahme durchzuführen.

So erhalten Sie den Vorteil von const, um zu verhindern, dass Sie versehentlich Änderungen erlauben, die das Design verhindern soll, aber Sie erlauben dennoch, dass das Objekt als Ganzes von einem anderen Objekt zugewiesen wird. Dadurch können Sie beispielsweise Objekte dieser Klasse in STL-Containern verwenden.

Sie könnten dies als eine spezielle Anwendung des 'pimpl' Idioms betrachten.

Etwas entlang der Linien von:

#include <algorithm> // for std::swap 

#include "boost/scoped_ptr.hpp" 

using namespace boost; 

class MyPacketT {}; 
class MyInfoT {}; 


class MyItemT 
{ 
public: 
    MyItemT(const MyPacketT& aMyPacket, const MyInfoT& aMyInfo) 
     : pMyPacket(new MyPacketT(aMyPacket)), 
      pMyInfo(new MyInfoT(aMyInfo)) 
    { 
    } 

    MyItemT(MyItemT const& other) 
     : pMyPacket(new MyPacketT(*(other.pMyPacket))), 
      pMyInfo(new MyInfoT(*(other.pMyInfo))) 
    { 

    } 

    void swap(MyItemT& other) 
    { 
     pMyPacket.swap(other.pMyPacket); 
     pMyInfo.swap(other.pMyInfo);   
    } 


    MyItemT const& operator=(MyItemT const& rhs) 
    { 
     MyItemT tmp(rhs); 

     swap(tmp); 

     return *this; 
    } 

private: 
    scoped_ptr<MyPacketT const> pMyPacket; 
    scoped_ptr<MyInfoT const> pMyInfo; 
}; 

Schließlich änderte ich mein Beispiel scoped_ptr<> statt shared_ptr<> zu verwenden, weil ich dachte, es wäre eine allgemeinere Darstellung dessen, was die OP vorgesehen. Jedoch, wenn die 'wiederzuerkennenden' const Mitglieder können gemeinsam genutzt werden (und das ist wahrscheinlich wahr, wenn ich verstehe, warum das OP sie will const), dann könnte es eine Optimierung sein,'s zu verwenden und die Kopier- und Zuweisungsvorgänge der Klasse kümmern Sie sich um Dinge für diese Objekte - wenn Sie keine anderen Mitglieder haben, die spezielle Kopien benötigen oder Semicate zuweisen, dann ist Ihre Klasse einfach viel einfacher geworden, und Sie könnten sogar einen beträchtlichen Speicherverbrauch sparen, indem Sie Kopien teilen können der MyPacketT und MyInfoT Objekte.

1

Ich denke, Sie könnten mit einem speziellen const Proxy bekommen.

template <class T> 
class Const 
{ 
public: 
    // Optimal way of returning, by value for built-in and by const& for user types 
    typedef boost::call_traits<T>::const_reference const_reference; 
    typedef boost::call_traits<T>::param_type param_type; 

    Const(): mData() {} 
    Const(param_type data): mData(data) {} 
    Const(const Const& rhs): mData(rhs.mData) {} 

    operator const_reference() const { return mData; } 

    void reset(param_type data) { mData = data; } // explicit 

private: 
    Const& operator=(const Const&); // deactivated 
    T mData; 
}; 

Statt nun const MyPacketT würden Sie Const<MyPacketT> haben. Nicht, dass die Schnittstelle nur eine Möglichkeit bietet, sie zu ändern: durch einen expliziten Aufruf an reset.

Ich denke, jede Verwendung von mMyPacket.reset kann leicht gesucht werden.Als @MSalters sagte es schützt gegen Murphy, nicht Machiavelli :)

Verwandte Themen