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

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

我正在尝试使用重载运算符创建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_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提供了跟踪指针(
    ^
    )和跟踪引用(
    %
    ),它们与垃圾收集器一起工作,完成两件事:

    • 确保在使用封闭对象时未释放该对象
    • 如果垃圾收集器移动封闭对象,则查找新地址
    <>从C++ +CLI使用,可以使<代码>操作符+<代码>非成员,就像普通C++。< /P>
    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_位置上使用运算符重载,则会出现编译错误。如果您想避免值复制语义,那么应该声明一个
      ref类
      。代码中的hat^
    • 在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吗