2013-08-02 18 views
2

我想用重载的操作符创建一个CLI值类c_Location,但我认为我有一个拳击问题。我已经实现了操作符重载,如许多手册中所见,所以我相信这一定是正确的。 这是我的代码: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+ (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' 
} 

搜索较长时间后,我发现错误来自操作数被引用类型,因为它是一个值类型的数组元素,并且函数只接受值类型,因为它需要非托管引用。我现在有2个possibiblities:

  1. 添加开箱浇铸到c_Location等改变故障线路在main()到
    locValue = alocData[0] + (c_Location)alocData[1];
  2. 修改操作员+重载,以便它通过值,而不是通过采用参数参考:
    c_Location operator+ (const c_Location i_locValue)

两个选项工作,但据我所看到的,他们都有缺点:
选择1意味着我必须在需要的地方明确施放。
选择2意味着该函数将在其调用中创建参数的副本,因此浪费性能(尽管不多)。

我的问题:我的失败分析是否正确或失败是否有其他原因?
有更好的第三个选择吗?
如果不是:哪个选项1或2更好?我现在更喜欢#2。

回答

1

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+和结果分配回原来的对象。


对于基本类型,如doubleint,你可能会发现你需要使用%还,但前提是你需要的是基本类型的实例的引用存储在管理对象中:

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 
+0

它的工作原理,谢谢!但我现在有点困惑:这是否意味着我将使用跟踪引用,尽管我没有创建gc对象?我是否也应该使用非托管类型的托管参考?: 'c_Location&operator + =(const double%i_dValue)'? 还是我完全错了,所有类型将被编译为托管类型(双 - > System :: Double?)? –

+0

@Tobias:让我扩展一下我的答案,以涵盖您的原始诊断,该诊断接近但不完全正确。 –

+0

@Tobias:好的,我完成了 –

2

规则是从本地C而不同++:

  • 的CLI要求该操作员重载是静态类的成员
  • 你可以在C++/CLI中使用const关键字,但是你没有得到它的里程,CLI不支持强制执行常量,并且没有其他.NET语言支持它。
  • 传递值类型的值应该按值完成,这就是首先在.NET中创建值类型的要点。使用&引用是麻烦,这是垃圾收集器无法调整的运行时本机指针。如果您尝试在托管类中嵌入的c_Location上使用运算符重载,则会发生编译错误。如果你想避免值复制语义,那么你应该声明一个ref class。 ^代码中的帽子^
  • 您在C++/CLI中创建的任何互操作类型应声明为public因此它可用于其他程序集和.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()中的代码无问题地编译。