Есть ли смысл для реализации функций копирования-оператор присваивания в классе со всеми константные данные-члены?


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

Вот как может выглядеть оболочка этого класса:

class Vector {
  public:
  Vector(float _x, float _y);

  private:
  const float x;
  const float y;
};

Я хотел бы иметь возможность присвоить новое значение векторному члену в пределах (например) класса. Экранная сущность с позицией, возможно, представленная с помощью неконстантного члена типа Vector.

Эта "сущность" сама должна быть изменчивой (возможно, поддерживающей движение), например, внутренний член Vector, используемый для хранения ее положения, должен измениться. Вместо того чтобы изменять этот элемент напрямую, я бы предпочел заменить его совершенно новым экземпляром Vector, инициализированным измененными значениями.

В таком языке, как C#, я бы просто вычислил новые значения для X и Y на основе текущего экземпляра Vector и назначил новый векторный объект, инициализированный этими значениями член, отбрасывая Предыдущее значение и позволяя GC сделать остальное.

// Inside some class (C#/Java type)...
Vector position;
...
// Example function:
void Move(float _dX, float _dY) {
  this.position = new Vector(_dX + position.X, _dY + position.Y);
}
Именно здесь в игру вступает моя ограниченная способность С++, потому что я не уверен, как реализовать operator= таким образом, чтобы экземпляры этого неизменяемого типа, содержащиеся в динамической памяти, не просто "исчезали" и не вызывали утечку.

Итак, я полагаю, что спрашиваю вот о чем:

Vector& operator=(const Vector& _rhs);
...
// In implementation file...
Vector& Vector::operator=(const Vector& _rhs) {
  // WHAT ON EARTH GOES IN HERE?!?!?
}

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

Не трачу ли я впустую свое время, пытаясь смоделировать неизменяемые типы?

Есть ли лучшее решение?

6 2

6 ответов:

Когда вы набираете

Vector position;

В определении класса вы определяете экземпляр класса Vector, который останется здесь навсегда. Если он неизменен, то ты облажался.

В C# / Java нет способа сделать это. Вместо этого вы определяете ссылки.

Если вы хотите сделать то же самое в C++, вам нужны указатели или auto_pointers

Vector* position;
Таким образом, вы можете изменить экземпляр, на который указывает position.

Не следует определять оператор присваивания a неизменяемый класс. Если вам действительно нужно, возможно, вам следует сделать Vector изменяемым и использовать const Vector, Когда вам нужен неизменяемый Vector. Вам просто нужно будет добавить const квалификаторы к методу, которые разрешены с помощью const Vector.

Например:

class Vector {
    private:
    float x;
    float y;
    public:
    // allowed for const Vector
    // because of the const before the '{'
    float norm() const {
        return hypot(x,y);
    }

    Vector& operator=(const Vector& o) {
        x=o.x;
        y=o.y;
        return *this;
    }
};

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

Обычно ваш оператор= хотел бы что-то вроде:

Vector::operator=(const Vector &rhs)
{
   x = rhs.x;
   y = rhs.y;
}

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

Этот оператор = перегрузка не будет работать для вашего сценария, b/c члены данных x и y не будут переназначать b/c, которые у вас есть как const. Здесь вы будете использовать конструктор копирования со списком инициализации. Если бы вы действительно хотели иметь operator=, вы могли бы использовать const_cast внутри перегрузки, но большинство посчитало бы эту форму плохой.

Vector::Vector(const Vector &copy)
   :x(copy.x), y(copy.y)
{

}

* Примечание компилятор также автоматически создаст этот тип конструктора копирования для вас.

Тогда ваш код создаст объект с помощью

Vector newObj(oldObj);

Изложив все это, чтобы ответить на ваш вопрос: почему бы просто не позволить членам быть неконстантными и не объявить вектор const?

const Vector myVector(5,10);

У вас есть логическое несоответствие в ваших требованиях. С одной стороны, все объекты класса Vector должны быть неизменяемыми, но с другой стороны вы хотите иметь возможность изменять их. Эти два требования находятся в прямом противоречии друг с другом.

Лучше было бы сделать классы такими, что единственный способ изменить значение-это назначить ему новый объект класса:

class Vector {
public:
  Vector(float _x, float _y);
  /* Vector(const Vector&) = default; */
  /* Vector& operator=(const Vector&) = default; */

  float x() const;
  float y() const;
  /* No non-const accessors!! */
private:
  float x;
  float y;
};

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

Если вас беспокоит правило трех здесь, сделайте класс noncopyable или объявите оператор присваивания копии как private (и оставьте его неопределенным).

Если это не подходит для вашего типа данных, то это означает, что эти элементы данных не должны быть const.

Я думаю, что вы несколько неправильно понимаете const правильность. Если вы действительно хотите иметь неизменяемую структуру, то оператор= не имеет смысла.

Однако я не вижу, почему приведенный ниже код не соответствует вашим потребностям:

struct Vector
{
    Vector(float, float);
    Vector(Vector const&);

private:
    const float x, y;

    // Disable automatic generation of operator=
    Vector& operator=(Vector const&);
};

Это должно сработать.

Vector& Vector::operator=(const Vector& _rhs) {
    Vector tmp( _rhs.x, _rhs.y );
    std::swap( *this, tmp );
    return *this;
}