2010-04-12 7 views
6

Mam kilku członków w mojej klasie, które są const i dlatego może być zainicjowane tylko poprzez listy initialiser tak:umożliwić państwom być const przy jednoczesnym wspieraniu operatora = od klasy

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

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

Moja klasa może być używane w niektórych z naszych wewnętrznie zdefiniowanych klas kontenerów (np. wektorów), a te kontenery wymagają, aby klasa operator= została zdefiniowana.

Oczywiście, moja operator= musi zrobić coś takiego:

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

co oczywiście nie działa, ponieważ mMyPacket i mMyInfoconst członków.

Inni, niż czynią tych członków nie-const (których nie chcę robić), żadnych pomysłów na temat tego, jak mogę to naprawić?

+8

Jeśli twoja klasa jest przypisywalna i tych członków można zmienić, zabrać "const". To po prostu nie ma sensu. – GManNickG

+0

@GMan - Myślę, że masz rację. Szukałem pewnego rodzaju podejścia podobnego do znajomego, które pozwala operatorowi '' mieć specjalne uprawnienia, które nadal uniemożliwiają ręczne zastępowanie członków. Ale po przeczytaniu tego, co powiedział @MichaelMrozek, nie jest to wcale bezpieczne. – LeopardSkinPillBoxHat

Odpowiedz

7

W pewnym sensie naruszasz definicję const, jeśli masz operatora przypisania, który może je zmienić po zakończeniu budowy. Jeśli naprawdę potrzebujesz, myślę, że nowa metoda umieszczania Potatoswatter jest prawdopodobnie najlepsza, ale jeśli masz operatora przypisania twoje zmienne nie są tak naprawdę const, ponieważ ktoś mógłby po prostu utworzyć nowe wystąpienie i użyć go do zmiany ich wartości

+0

@Michael - Prawda i prawidłowy punkt. – LeopardSkinPillBoxHat

+1

Z drugiej strony, nic nie przeszkadza użytkownikowi w ręcznym wykonaniu takiego zła. (Lub po prostu używając 'const_cast'!) Z pewnością udokumentuj, że' operator = 'unieważnia odniesienia do elementów. – Potatoswatter

+1

Cóż, pomiędzy 'const_cast' i' mutable' specifier można całkiem całkowicie zniszczyć koncepcję zmiennych stałych :). Niewiele C/C++ faktycznie uniemożliwi ci wykonanie, jeśli zmusisz go do –

2

To brudna hack, ale można zniszczyć i odbudować siebie:

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: bym zniszczyć moją wiarygodność, ja nie faktycznie to zrobić sam, chciałbym usunąć const. Jednak debatowałem nad zmianą praktyki, ponieważ jest ona użyteczna i lepsza w użyciu, gdy tylko jest to możliwe.

Czasami istnieje rozróżnienie między zasobem a wartością reprezentowaną przez obiekt. Członek może być uzależniony od zmian wartości, o ile zasób jest taki sam i byłoby miło, gdybyśmy mieli na to czas.

Edit 2: @Charles Bailey dostarczył tę wspaniałą (i wysoce krytyczną) link: http://gotw.ca/gotw/023.htm.

  • Semantyka jest trudna w dowolnej klasie pochodnej operator=.
  • Może być nieefektywna, ponieważ nie wywołuje zdefiniowanych operatorów przypisania.
  • Jest to niezgodne z wonky operator& przeciążeń (cokolwiek)
  • itp

Edit 3: Myśląc przez "który zasób" vs "Jaką wartość" różnicy, wydaje się jasne, że operator= powinien zawsze zmiany wartość, a nie zasób. Identyfikator zasobu może wtedy być const. W tym przykładzie wszyscy członkowie to const. Jeśli "informacja" jest tym, co jest przechowywane w "pakiecie", to może pakiet powinien być const, a informacje nie.

Problem polega więc na tym, że nie rozumiemy semantyki przypisania jako oczywistej wartości w tym przykładzie, jeśli "informacje" są w rzeczywistości metadanymi. Jeśli jakakolwiek klasa posiada numer MyItemT, chce go przełączać z jednego pakietu na inny, musi albo zrezygnować i użyć zamiast tego auto_ptr<MyItemT>, albo użyć podobnego hacka, jak wyżej (test tożsamości jest niepotrzebny, ale pozostało tylko catch) zaimplementowanego jako z poza.Ale operator= nie powinien zmieniać powiązania zasobów, z wyjątkiem wyjątkowej funkcji, która absolutnie nie będzie kolidować z niczym innym.

Należy pamiętać, że ta konwencja jest zgodna z poradą Sutter dotyczącą wdrażania konstrukcji kopii pod względem przydziału.

+2

@Potatoswatter - Dzięki za sugestię, ale myślę, że jest to prawdopodobnie bardziej brudne niż sprawianie, by członkowie nie byli const. – LeopardSkinPillBoxHat

+0

@ Leopard: rozumiem, ale z drugiej strony nie ma tu nic naprawdę niebezpiecznego. W pewnym sensie masz dodatkową gwarancję, że nie używasz stanu nieaktualnego. – Potatoswatter

+3

Jeśli konstruktor kopiowania rzuci, możesz być skazany na zagładę, ponieważ jeśli chodzi o język, '* this' będzie nieuchronnie zniszczony ponownie. – UncleBens

3

Zamiast bezpośrednio przechowywać obiekty w kontenerach, możesz przechowywać wskaźniki (lub inteligentne wskaźniki). W ten sposób nie musisz mutować żadnego z członków klasy - otrzymujesz dokładnie ten sam obiekt, co przekazałeś, const i wszystkie.

Oczywiście, prawdopodobnie spowoduje to zmianę w zarządzaniu pamięcią aplikacji, co może być dobrym powodem, aby tego nie chcieć.

+0

@Andrew - Interesująca sugestia, ale prawdopodobnie nie jest praktyczna w moim przypadku. Moje argumenty konstruktora są obiektami przydzielonymi do stosu, więc muszę wziąć ich kopię do mojej klasy ze względu na problemy z życiem (ponieważ moja klasa przeżyje obiekty stosu). – LeopardSkinPillBoxHat

+0

@LeopardSkinPillBoxHat: nawet jeśli klasa utrzymuje wskaźniki, nie oznacza to, że będziesz mieć problemy z życiem - klasa będzie nadal kopiować te dane do swoich własnych przydziałów, po prostu będą one "nowe" z kupy z wskaźniki (lub inteligentne wskaźniki) do dynamicznie przydzielanych bitów zamiast posiadania kopii bezpośrednio w obiekcie. –

1

Możliwe, że rozważenie nadania członkom MyPacketT i MyInfoT wskaźników na const (lub inteligentnych wskaźników do const). W ten sposób dane same w sobie są nadal oznaczone jako const i immutable, ale możesz je "zamienić" na inny zestaw danych stałych w przypisaniu, jeśli ma to sens. Właściwie możesz użyć idiomu swap do wykonania przypisania w sposób bezpieczny dla wyjątków.

Dzięki temu można uzyskać korzyści z const, aby zapobiec przypadkowemu zezwoleniu na zmiany, których projekt ma zapobiegać, ale nadal można zezwolić na przypisanie obiektu jako całości z innego obiektu. Na przykład pozwoli ci to używać obiektów tej klasy w kontenerach STL.

Możesz na to spojrzeć jako na specjalną aplikację z idiomu "pimpl".

Coś wzdłuż linii:

#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; 
}; 

Wreszcie zmieniłem przykład do korzystania scoped_ptr<> zamiast shared_ptr<> bo myślałem, że to bardziej ogólnego reprezentacja co OP przeznaczeniem. Jednakże, jeśli członkowie "reassignable" mogą zostać udostępnieni (i to prawdopodobnie prawda, biorąc pod uwagę moje zrozumienie, dlaczego OP chce ich uzyskać const), może to być optymalizacja do korzystania z shared_ptr<> i pozwolić na kopiowanie i przypisywanie operacji. Klasa shared_ptr<> dba o rzeczy związane z tymi obiektami - jeśli nie masz innych członków, którzy wymagają specjalnej kopii lub przypiszą semicry, wtedy Twoja klasa jest o wiele prostsza, a możesz nawet zaoszczędzić znaczną część pamięci dzięki możliwości udostępniania kopii obiektów MyPacketT i MyInfoT.

1

Myślę, że mógłbyś uciec ze specjalnym serwerem proxy const.

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; 
}; 

Teraz, zamiast const MyPacketT trzeba Const<MyPacketT>. Nie oznacza to, że interfejs zapewnia tylko jeden sposób zmiany: poprzez jawne wywołanie pod numerem reset.

Myślę, że każde użycie mMyPacket.reset można łatwo wyszukać.Jako @MSalters powiedział, że chroni przed Murphy, a nie Machiavelli :)

Powiązane problemy