Позвольте члену быть const, все еще поддерживая operator = на классе - PullRequest
6 голосов
/ 12 апреля 2010

У меня в классе несколько членов, которые const и поэтому могут быть инициализированы только через список инициализаторов, например:

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

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

Мой класс может использоваться в некоторых из наших внутренних контейнерных классов (например, векторов), и эти контейнеры требуют, чтобы operator= было определено в классе.

Конечно, мой operator= должен сделать что-то вроде этого:

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

что, конечно, не работает, потому что mMyPacket и mMyInfo являются const членами.

Кроме того, что эти члены не являются const (что я не хочу делать), есть идеи о том, как я могу это исправить?

Ответы [ 5 ]

7 голосов
/ 12 апреля 2010

Вы как бы нарушаете определение const, если у вас есть оператор присваивания, который может изменить их после завершения построения. Если вам действительно нужно, я думаю, что новый метод размещения Potatoswatter , вероятно, лучше, но если у вас есть оператор присваивания, ваши переменные на самом деле не являются константными, поскольку кто-то может просто создать новый экземпляр и использовать его изменить свои значения

3 голосов
/ 12 апреля 2010

Вместо того, чтобы хранить объекты непосредственно в ваших контейнерах, вы можете хранить указатели (или умные указатели). Таким образом, вам не нужно мутировать ни одного из членов вашего класса - вы получите точно такой же объект, как вы прошли, const и все.

Конечно, выполнение этого, вероятно, несколько изменит управление памятью вашего приложения, что может быть достаточно веской причиной, чтобы этого не хотеть.

2 голосов
/ 12 апреля 2010

Это грязный хак, но вы можете уничтожить и восстановить себя:

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

Редактировать: чтобы я не разрушил свой авторитет, на самом деле я сам этого не сделаю, я бы удалил const. Тем не менее, я спорил об изменении практики, потому что const просто полезен и его лучше использовать везде, где возможно.

Иногда существует различие между ресурсом и значением, представленным объектом. Член может быть подвержен изменениям значения до тех пор, пока ресурс остается тем же, и было бы неплохо обеспечить безопасность во время компиляции.

Редактировать 2: @Charles Bailey предоставил эту замечательную (и очень критическую) ссылку: http://gotw.ca/gotw/023.htm.

  • Семантика сложна в любом производном классе operator=.
  • Это может быть неэффективно, поскольку не вызывает определенные операторы присваивания.
  • Он несовместим с перегрузками Wickky operator& (что угодно)
  • и т.д.

Редактировать 3: Обдумывая разницу между «какой ресурс» и «какое значение», кажется очевидным, что operator= всегда должно менять значение, а не ресурс. Тогда идентификатор ресурса может быть const. В этом примере все члены const. Если «информация» - это то, что хранится внутри «пакета», то, возможно, пакет должен быть const, а информация - нет.

Таким образом, проблема не столько в выяснении семантики присваивания, сколько в отсутствии очевидного значения в этом примере, если «информация» на самом деле является метаданными. Если какой-либо класс, владеющий MyItemT, хочет переключить его с одного пакета на другой, ему нужно либо отказаться от использования auto_ptr<MyItemT>, либо прибегнуть к такому же взлому, как указано выше (проверка идентичности не нужна, но catch остальное) реализовано извне . Но operator= не должен изменять привязку ресурса, кроме как сверхспециальная функция, которая абсолютно не мешает ничему другому.

Обратите внимание, что это соглашение хорошо согласуется с рекомендацией Саттера о реализации конструкции копирования с точки зрения назначения.

 MyItemT::MyItemT( MyItemT const &in )
     : mMyPacket( in.mMyPacket ) // initialize resource, const member
     { *this = in; } // assign value, non-const, via sole assignment method
1 голос
/ 12 апреля 2010

Я думаю, вы могли бы воспользоваться специальным прокси 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;
};

Теперь вместо const MyPacketT у вас будет Const<MyPacketT>. Не то, чтобы интерфейс предоставлял только один способ изменить его: через явный вызов reset.

Я думаю, что любое использование mMyPacket.reset может быть легко найдено. Как сказал @MSalters, он защищает от Мерфи, а не Макиавелли :)

1 голос
/ 12 апреля 2010

Возможно, вы захотите, чтобы члены MyPacketT и MyInfoT были указателями на const (или умными указателями на const). Таким образом, сами данные по-прежнему помечаются как постоянные и неизменные, но вы можете просто «поменять» другой набор константных данных в назначении, если это имеет смысл. Фактически, вы можете использовать идиому подкачки для выполнения назначения безопасным образом.

Таким образом, вы получаете преимущество const, помогающее предотвратить случайное разрешение изменений, которые вы хотите предотвратить в дизайне, но при этом вы все же можете назначить объект в целом из другого объекта. Например, это позволит вам использовать объекты этого класса в контейнерах STL.

Вы можете рассматривать это как специальное применение идиомы pimpl.

Что-то вроде:

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

Наконец, я изменил свой пример, чтобы использовать scoped_ptr<> вместо shared_ptr<>, потому что я думал, что это было более общее представление о том, что намеревался OP. Однако, если «переназначаемые» члены const могут быть разделены (и это, вероятно, верно, учитывая мое понимание того, почему ОП хочет их const), то это может быть оптимизация, чтобы использовать shared_ptr<> и позволить Операции копирования и присваивания класса shared_ptr<> позаботятся о вещах для этих объектов - если у вас нет других членов, которые требуют специального копирования или присваивания семантики, то ваш класс просто стал намного проще, и вы могли бы даже сохранить значительный бит использование памяти благодаря возможности совместного использования копий объектов MyPacketT и MyInfoT.

...