在具有所有const数据成员的类中实现复制赋值运算符是否有意义?

时间:2010-09-07 12:57:02

标签: c++ class-design

想象一下,我有一个类用于表示一些琐碎的数值数据,比如矢量。我希望这个类在其成员方面是不可变的,但仍然支持正确的拷贝分配行为。

这个类的shell可能是这样的:

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

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

我希望能够在(例如)类中为向量成员分配新值。具有位置的屏幕实体,可能使用类型为Vector的非const成员表示。

这个“实体”本身必须是可变的(可能支持运动),例如用于存储其位置的内部Vector成员必须更改。我不是直接修改这个成员,而是用一个全新的Vector实例替换它,用修改后的值初始化。

在C#等语言中,我只是根据当前Vector实例计算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);
}

这是我使用C ++的有限能力发挥作用的地方,因为我不确定如何以这样的方式实现operator=,动态内存中包含的这种不可变类型的实例不会简单地“消失”并导致泄漏。

所以,我想,我要问的是:

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

Vector案例是我发明的事情,可以解释我的观点,我可以想到在会员级别应该是不可变的多种类型。

我是否在浪费时间尝试为不可变类型建模?

有更好的解决方案吗?

6 个答案:

答案 0 :(得分:3)

键入

Vector position;

在类定义中,您定义了一个永远留在这里的Vector类的实例。如果那个是不可改变的,你就搞砸了。

在C#/ Java中,没有办法做到这一点。而是定义引用。

如果你想在C ++中做同样的事情,你需要指针或auto_pointers

Vector* position;

这样,您可以更改position指向的实例。

您不应该定义不可变类的赋值运算符。如果你真的需要,也许你应该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;
    }
};

答案 1 :(得分:2)

您尝试做的事与使用operator =无关。原因是你需要一个Vector类的实例才能使用operator =,但你已经声明你不想允许更改内部。

通常情况下,您的运营商=会喜欢以下内容:

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

*注意编译器会自动为你生成这个,因为你没有指针等就安全了。

此operator = overload不适用于您的场景,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并声明Vector const?

const Vector myVector(5,10);

答案 2 :(得分:2)

您的要求存在逻辑上的不一致。 一方面,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;
};

初始化后,无法修改此类的实例,除非使用新实例完全覆盖它们。

答案 3 :(得分:1)

如果您在这里关注三级规则,请创建类noncopyable,或将复制赋值运算符声明为private(并保持未定义)。

如果这不适合您的数据类型,那么这表明这些数据成员不应该是const

答案 4 :(得分:0)

我认为你在某种程度上误解了const的正确性。如果你真的想要一个不可变的结构,那么拥有一个operator =是没有意义的。

但是,我不明白为什么下面的代码不符合您的需求:

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

private:
    const float x, y;

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

答案 5 :(得分:0)

这应该这样做。

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