C ++ / CLI:如何重载运算符以接受引用类型?

托比亚斯·克诺斯(Tobias Knauss)

我正在尝试使用重载运算符创建CLI值类c_Location,但是我认为拳击有问题。我已经实现了很多手册中都介绍过的运算符重载,因此我确信这一定是正确的。这是我的代码:

value class c_Location
{
public:
  double x, y, z;
  c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

  c_Location& operator+= (const c_Location& i_locValue)
  {
    x += i_locValue.x;
    y += i_locValue.y;
    z += i_locValue.z;
    return *this;
  }
  c_Location operator+ (const c_Location& i_locValue)
  {
    c_Location locValue(x, y, z);
    return locValue += i_locValue;
  }
};

int main()
{
  array<c_Location,1>^ alocData = gcnew array<c_Location,1>(2);
  c_Location locValue, locValue1, locValue2;
  locValue = locValue1 + locValue2;
  locValue = alocData[0] + alocData[1];  // Error C2679 Binary '+': no operator found which takes a right-hand operand of type 'c_Location'
}

搜索更长的时间后,我发现错误来自操作数是引用类型,因为它是值类型的数组元素,并且该函数仅接受值类型,因为它接受了非托管引用。我现在有两种可能性:

  1. 向其中添加拆箱强制c_Location转换,并将main()中的错误行更改为
    locValue = alocData[0] + (c_Location)alocData[1];
  2. 修改operator +重载,以便它按值而不是按引用接受参数:
    c_Location operator+ (const c_Location i_locValue)

这两个选项都有效,但据我所知,它们都有缺点:
opt 1表示我必须在需要的地方进行显式转换。
opt 2表示该函数将在调用时创建该参数的副本,因此会浪费性能(虽然不多)。

我的问题:我的故障分析是完全正确的还是故障还有其他原因?
有更好的第三种选择吗?
如果不是:哪个选项更好1或2?我目前更喜欢#2。

本·沃格特

TL; DR版本:

对于托管代码,请使用%按引用传递参数,而不是&


您的诊断并不完全正确。拳击与您的问题无关。但是引用类型确实可以做到。

当您说“我发现错误来自操作数是引用类型”时,您真的很接近。好吧,操作数是一个值类型,而不是引用类型。但是,当操作数存储引用类型内部时会发生错误,因为该操作数位于垃圾收集的堆中(放置引用类型的所有实例)。这适用于数组以及包含值类型成员的您自己的对象。

危险在于,当垃圾收集器运行时,它可以在gc堆上移动项目。这会破坏本机指针(*)和引用(&),因为它们存储地址并希望其永远保持不变。为了解决此问题,C ++ / CLI提供了跟踪指针(^)和跟踪引用(%),它们与垃圾回收器一起工作以完成两件事:

  • 确保在使用封闭对象时未释放它
  • 如果垃圾收集器移动了封闭对象,则找到新地址

要在C ++ / CLI中使用,可以operator+像普通C ++一样使它成为非成员。

value class c_Location
{
public:
    double x, y, z;
    c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

    c_Location% operator+= (const c_Location% i_locValue)
    {
        x += i_locValue.x;
        y += i_locValue.y;
        z += i_locValue.z;
        return *this;
    }
};

c_Location operator+ (c_Location left, const c_Location% right)
{
    return left += right;
}

缺点是C#不会使用非成员,为了与C#兼容,请像非成员运算符(带有两个显式操作数)一样编写它,但使其成为公共静态成员。

value class c_Location
{
public:
    double x, y, z;
    c_Location (double i_x, double i_y, double i_z) : x(i_x), y(i_y), z(i_z) {}

    c_Location% operator+= (const c_Location% i_locValue)
    {
        x += i_locValue.x;
        y += i_locValue.y;
        z += i_locValue.z;
        return *this;
    }

    static c_Location operator+ (c_Location left, const c_Location% right)
    {
        return left += right;
    }
};

operator+=由于C#仍然无法识别它,因此没有理由担心,它将使用operator+并将结果分配回原始对象。


对于诸如double或的原始类型int,您可能会发现还需要使用它%,但前提是您需要引用该原始类型的实例存储在托管对象中:

double d;
array<double>^ a = gcnew darray<double>(5);
double& native_ref = d; // ok, d is stored on stack and cannot move
double& native_ref2 = a[0]; // error, a[0] is in the managed heap, you MUST coordinate with the garbage collector
double% tracking_ref = d; // ok, tracking references with with variables that don't move, too
double% tracking_ref2 = a[0]; // ok, now you and the garbage collector are working together

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章