Как мне назначить объект данных с членами const?

Надеюсь, это не дубликат. Если это так, пожалуйста, укажите мне на это в комментарии, и я снова удалю вопрос.

У меня есть объект данных с данными, которые действительны только в пакете, т. е. нет смысла изменять значение одного члена, не делая недействительными другие элементы.
Этот объект данных описывает некоторую информацию об изображении:

struct ImageInfo
{
    ImageInfo(const double &ppix, ...)
        : PpiX(ppix),
        ...
    { }

    const double PpiX;
    const double PpiY;
    const int SizeX;
    const int SizeY;
};

В моем объекте изображения у меня есть неконстантный элемент типа ImageInfo:

class MyImageObject
{
    ...
private:
    ImageInfo mMyInfo;
}

Я хочу иметь возможность изменять mMyInfo во время выполнения, но только так, чтобы он принимал новый экземпляр ImageInfo(...).

В функции MyImageObject::Load() я хотел бы прочитать эти данные из информации о файле, а затем создать экземпляр ImageInfo с правильным набором данных:

double ppix = ImageFile.GetPpiX();
...
mMyInfo = ImageInfo(ppix, ...);

Но мне не удалось написать допустимый оператор присваивания (Конструктор копирования возможен). Мое решение оставило mMyInfo пустым, потому что я не ссылался на this:

ImageInfo operator=(const ImageInfo &other)
{
    // no reference to *this
    return ImageInfo(other);
}

Из любопытства я хотел бы знать, как должен выглядеть оператор присваивания для такого класса.

Я использую простой C++.

ИЗМЕНИТЬ
Возможные решения (цель состоит в том, чтобы данные были переносимыми, но связными):

  • Используйте закрытые члены вместе с Get...() функциями -> просто, но я бы хотел избежать круглых скобок.
  • Сохраните указатель на ImageInfo: ImageInfo *mpMyInfo; (я бы хотел избежать кучи.)
  • Используйте сериализацию и сохраните сериализованный ImageInfo, а затем создайте локальные экземпляры из сериализованных данных.

person Martin Hennings    schedule 11.06.2013    source источник
comment
Вы можете попробовать отказаться от «константности» переменных-членов, чтобы выполнить назначение, asfaik, это допустимо, когда сам объект не является константным.   -  person Gearoid Murphy    schedule 11.06.2013
comment
Почему эти поля const для начала?   -  person    schedule 11.06.2013
comment
@delnan Им не обязательно быть const, им нужно быть readonly. Но readonly существует в C#, но не в C++.   -  person Martin Hennings    schedule 11.06.2013
comment
Будь то const или readonly, вы, очевидно, хотите изменить эти значения с течением времени, так зачем заявлять, что они не меняются?   -  person    schedule 11.06.2013
comment
@delnan Думаю, я слишком много думаю об объекте. Правильно ли следующее? Элемент mMyInfo находится в стеке, поэтому его элементы занимают поля стека, помеченные константой. В C++ невозможно заменить экземпляр mMyInfo другим экземпляром ImageInfo, верно?   -  person Martin Hennings    schedule 11.06.2013


Ответы (4)


Я не думаю, что у вас могут быть константные переменные-члены, которые не являются статическими. Если вам нужны константные переменные, которые изменяются вместе с экземпляром, вы можете сделать что-то вроде этого:

struct ImageInfo
{
private:
    double myPpiX;
    double myPpiY;
    int mySizeX;
    int mySizeY

public:
    ImageInfo(const double &ppix, ...)
        : myPpiX(ppix),
          PpiX(myPpiX),
        ...
    { }

    ImageInfo( const ImageInfo &other)
    : myPpiX( other.myPpiX),
      PpiX(myPpiX)
     ...
    { }

    const double &PpiX;
    const double &PpiY;
    const int &SizeX;
    const int &SizeY;

    // EDIT: explicit assignment operator was missing
    ImageInfo& operator=(const ImageInfo &other)
    {
        myPpiX  = other.myPpiX;
        myPpiY  = other.myPpiX;
        mySizeX = other.mySizeX;
        mySizeX = other.mySizeX;
        return *this;
    }
};

Значения хранятся в закрытых переменных, которые можно установить при построении, а доступ к их значениям осуществляется с помощью константных ссылок. Вы также не зависите от ссылок, переданных в конструктор, живущих до тех пор, пока экземпляр ImageInfo.

person HeywoodFloyd    schedule 11.06.2013
comment
Это не разрешает operator=, потому что ссылки не могут быть переустановлены. - person Yakk - Adam Nevraumont; 11.06.2013
comment
Якк, хорошая мысль. Вам нужно будет написать оператор = и конструктор копирования. - person HeywoodFloyd; 11.06.2013
comment
@Yakk Так можно ли написать для этого оператор присваивания? - person Martin Hennings; 11.06.2013
comment
@Martin Мартин, я изначально думал, что нет, но, может быть, ты сможешь. Ссылки не могут быть переустановлены, но, возможно, мы не хотим, чтобы они переустанавливались! - person Yakk - Adam Nevraumont; 11.06.2013
comment
@Yakk Я реализовал эту идею, и она действительно отлично работает. Я отредактирую ответ HeywoodFloyd, добавив отсутствующий оператор присваивания. - person Martin Hennings; 12.06.2013
comment
@HeywoodFloyd Это ответ, который я искал. Жаль, что я могу только один раз проголосовать за вас... - person Martin Hennings; 12.06.2013

Поскольку это поля данных, которые можно изменить, они не являются const.

Если вы хотите ограничить доступ к ним после создания до const, вам нужно обернуть их в аксессоры следующим образом:

struct ImageInfo
{
  ImageInfo(const double &ppix, /*...*/)
    : PpiX_(ppix),
    /*...*/
  { }
  double const& PpiX() const {return PpiX_; };
  double const& PpiY() const {return PipY_; };
  int const& SizeX() const {return SizeX_; };
  int const& SizeY() const {return SizeY_; };
private:
  double PpiX_;
  double PpiY_;
  int SizeX_;
  int SizeY_;
};

Это позволяет перемещать/копировать присваивание и построение, блокируя не-const доступ за пределами указанного построения.

Избежать () сложно, но это можно сделать с помощью псевдоссылок, например:

struct pseudo_const_reference_to_Ppix {
  ImageInfo const* self;
  operator double() const { return self->Ppix; }
  void reseat( ImageInfo const* o ) { self = o; }
};

плюс целая куча шаблонов для перегрузки каждого оператора const слева и справа, чтобы указанный выше pseudo_const_reference_* был так же действителен, как double.

Общие версии могут быть написаны (используя либо функтор, либо std::function, если вы готовы терпеть накладные расходы на стирание типа).

Затем вы поддерживаете эти псевдо-const ссылки при назначении и копировании/перемещении конструкции.

Я думаю, что () лучший вариант.

Обратите внимание, что накладные расходы указателя (или более) на псевдоссылку в основном неизбежны: переменные-члены не имеют доступа к this, из которого они вызываются, даже если запрашивающий сайт имеет это прямо как день.

person Yakk - Adam Nevraumont    schedule 11.06.2013
comment
@Martin Мартин, теперь я включил набросок нелепого решения, которое избавляет от (). - person Yakk - Adam Nevraumont; 11.06.2013

Если что-то является константой, вы не можете это изменить. Полная остановка.

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

Обычно члены const задаются в конструкторе. Вы можете создать функцию загрузки, которая создает объект MyImageObject со всем его содержимым, избегая того, чтобы делать что-то наполовину и загружать состояние на втором этапе.

Альтернативой является косвенное получение mMyInfo, скажем, с использованием unique_ptr, после чего вы можете заменить его другим экземпляром. Я бы не стал этого делать без веской причины.

person Balog Pal    schedule 11.06.2013

Неизменяемые объекты-значения великолепны. Но переменная, содержащая объект значения (mMyInfo в MyImageObject), должна быть (непостоянной) указателем. В других языках (например, Java) это происходит автоматически, но не в C++, где вам нужен оператор *. Кроме того, нет необходимости переопределять/реализовывать оператор = для объектов-значений. Чтобы изменить данные изображения в объекте изображения, вы назначаете вновь созданный объект ImageInfo указателю myImageInfo. Таким образом, ни одна из внутренних переменных объекта значения не изменяется.

person donquixote    schedule 15.02.2015
comment
Если это только POD, который заменяет кучу переменных-членов одной, я думаю, что подход из ответа HeywoodFloyd идеален. Если есть необходимость поделиться этой информацией, вместо этого у меня будет общий указатель этого типа (инициализированный NULL). Привет Чили! - person Martin Hennings; 16.02.2015