2013-08-02 20 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()中的代碼無問題地編譯。