C++ cli C++/CLI:如何重载运算符以接受引用类型?
我正在尝试使用重载运算符创建CLI值类c_位置,但我认为装箱有问题。我已经实现了很多手册中提到的操作符重载,所以我确信这一定是正确的。 这是我的代码:C++ cli C++/CLI:如何重载运算符以接受引用类型?,c++-cli,operator-overloading,reference-type,C++ Cli,Operator Overloading,Reference Type,我正在尝试使用重载运算符创建CLI值类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_Locati
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_位置
{
公众:
双x,y,z;
c_位置(双i_x,双i_y,双i_z):x(i_x),y(i_y),z(i_z){
c_位置和运算符+=(常量c_位置和i_位置值)
{
x+=i_locValue.x;
y+=i_locValue.y;
z+=i_locValue.z;
归还*这个;
}
c_位置运算符+(常量c_位置和i_位置值)
{
c_位置值(x,y,z);
返回locValue+=i_locValue;
}
};
int main()
{
数组^ALOCATA=gcnew数组(2);
c_位置locValue、locValue1、locValue2;
locValue=locValue1+locValue2;
locValue=alocData[0]+alocData[1];//错误C2679二进制“+”:未找到接受类型为“c_Location”的右操作数的运算符
}
搜索较长时间后,我发现错误来自操作数是引用类型,因为它是值类型的数组元素,而函数只接受值类型,因为它接受非托管引用。我现在有两种可能性:
c_位置添加取消装箱转换,从而将main()中的错误行更改为
locValue=alocData[0]+(c_位置)alocData[1]代码>
c\u位置运算符+(常量c\u位置i\u位置值)
opt 1意味着我必须在需要的地方显式强制转换。
opt 2意味着函数将在调用时创建参数的副本,因此会浪费性能(不过不会太多) 我的问题:我的失败分析是正确的,还是失败有其他原因?
还有更好的第三种选择吗?
如果不是:1或2哪个选项更好?我现在更喜欢#2。TL;DR版本: 对于托管代码,使用
%
作为传递参考参数,而不是&
你的诊断不完全正确。拳击与你的问题无关。但在某种程度上,引用类型确实如此 当你说“我发现错误来自于操作数是引用类型”时,你真的很接近。操作数是一种值类型,而不是引用类型。但是当操作数存储在引用类型中时会发生错误,因为它位于垃圾收集堆(所有引用类型的实例都放置在垃圾收集堆中)。这适用于数组以及包含值类型成员的您自己的对象 危险在于,当垃圾收集器运行时,它可能会在gc堆上移动项目。这打破了本机指针(
*
)和引用(&
),因为它们存储地址并希望地址永远保持不变。为了处理这个问题,C++/CLI提供了跟踪指针(^
)和跟踪引用(%
),它们与垃圾收集器一起工作,完成两件事:
- 确保在使用封闭对象时未释放该对象
- 如果垃圾收集器移动封闭对象,则查找新地址
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
double-d;
数组^a=新的darray(5);
双本地_ref=d;//好,d存储在堆栈上,无法移动
双本地_ref2=a[0];//错误,[0]位于托管堆中,您必须与垃圾收集器协调
双%tracking_ref=d;//好的,使用不移动的变量跟踪引用
双%tracking_ref2=a[0];//好的,现在你和垃圾收集器正在一起工作
这些规则与本机C++有很大不同:
- CLI要求运算符重载是类的静态成员
- 您可以在C++/CLI中使用const关键字,但却无法从中获益,CLI不支持强制使用const,而且几乎没有其他.NET语言支持它李>
- 传递值类型的值应该由值来完成,这就是在.NET中首先使用值类型的意义。使用&reference非常麻烦,这是运行时垃圾收集器无法调整的本机指针。如果试图在托管类中嵌入的c_位置上使用运算符重载,则会出现编译错误。如果您想避免值复制语义,那么应该声明一个
。代码中的hat^ref类
- 在C++/CLI中创建的任何互操作类型都应声明为公共,以便可以从其他程序集和.NET语言中使用。还不完全清楚这是否是您的意图,这通常是您编写C++/CLI代码的原因
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()中的代码可以顺利编译。它可以正常工作,谢谢!但我现在有点困惑:这是否意味着我将使用跟踪引用,尽管我没有创建gc对象?我也应该使用托管ref吗