我正在尝试使用重载运算符创建一个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'
}
在搜索了更长的时间之后,我发现错误来自操作数是一个引用类型,因为它是一个值类型的数组元素,并且该函数只接受值类型,因为它需要一个非托管引用。我现在有两种可能性:
c_Location
,然后将main()中的错误行更改为locValue = alocData[0] + (c_Location)alocData[1];
c_Location operator+ (const c_Location i_locValue)
两种选择都有效,但据我所知,它们都有缺点:
opt 1意味着我必须在需要的地方明确投射。
opt 2意味着该函数将在其调用时创建参数的副本,因此浪费性能(尽管不多)。
我的问题:我的失败分析是否正确,或失败是否还有其他原因? 还有更好的第三种选择吗? 如果不是:哪个选项,1或2,哪个更好?我目前更喜欢#2。
答案 0 :(得分:2)
TL; DR版本:
对于托管代码,请使用%
作为参考传递参数,而不是&
您的诊断不完全正确。拳击与你的问题无关。但是参考类型在某种程度上确实存在。
当你说“我发现错误来自操作数是引用类型”时,你真的很接近。嗯,操作数是值类型而不是引用类型。但是当操作数存储在 in 引用类型中时会发生错误,因为它位于垃圾收集堆内(放置引用类型的所有实例)。这适用于数组以及包含值类型成员的自己的对象。
危险在于当垃圾收集器运行时,它可以在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
答案 1 :(得分:2)
这些规则与原生C ++有很大不同:
ref class
。代码中的帽子^。你可以让你的价值观看起来像这样:
public 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) {}
static c_Location operator+= (c_Location me, c_Location rhs)
{
me.x += rhs.x;
me.y += rhs.y;
me.z += rhs.z;
return me;
}
static c_Location operator+ (c_Location me, c_Location rhs)
{
return c_Location(me.x + rhs.x, me.y + rhs.y, me.z + rhs.z);
}
};
未经测试,应该接近。您现在将看到main()中的代码编译没有问题。