Позвольте участнику быть константой при тихой поддержке оператора = на классе

У меня есть несколько участников в моем классе, которые являются 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 (который я не хочу делать), какие-либо идеи о том, как я мог зафиксировать это?

6
задан LeopardSkinPillBoxHat 12 April 2010 в 06:50
поделиться

5 ответов

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

7
ответ дан 8 December 2019 в 17:19
поделиться

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

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

3
ответ дан 8 December 2019 в 17:19
поделиться

Думаю, можно обойтись специальным 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. Не то чтобы интерфейс предоставлял только один способ изменить его: через явный вызов reset.

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

1
ответ дан 8 December 2019 в 17:19
поделиться

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

1
ответ дан 8 December 2019 в 17:19
поделиться

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

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 = .
  • Это может быть неэффективным, поскольку не вызывает операторов присваивания, которые были определены.
  • Это несовместимо с ненадежным оператором и перегрузками (какими угодно)
  • и т. Д.

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

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

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

 MyItemT::MyItemT( MyItemT const &in )
     : mMyPacket( in.mMyPacket ) // initialize resource, const member
     { *this = in; } // assign value, non-const, via sole assignment method
2
ответ дан 8 December 2019 в 17:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: