2011-09-01 45 views
1

我通過創建自己的數據結構類(準確地說是一個矩陣)來教自己創建C++,並且我僅將它改爲僅使用雙精度的<T>類型的模板類。重載的矩陣運算符是相當標準C++模板類的運算符

// A snippet of code from when this matrix wasn't a template class 
    // Assignment 
    Matrix& operator=(const Matrix& other); 

    // Compound assignment 
    Matrix& operator+=(const Matrix& other); // matrix addition 
    Matrix& operator-=(const Matrix& other); // matrix subtracton 
    Matrix& operator&=(const Matrix& other); // elem by elem product 
    Matrix& operator*=(const Matrix& other); // matrix product 

    // Binary, defined in terms of compound 
    Matrix& operator+(const Matrix& other) const; // matrix addition 
    Matrix& operator-(const Matrix& other) const; // matrix subtracton 
    Matrix& operator&(const Matrix& other) const; // elem by elem product 
    Matrix& operator*(const Matrix& other) const; // matrix product 

    // examples of += and +, others similar 
    Matrix& Matrix::operator+=(const Matrix& rhs) 
    { 
     for(unsigned int i = 0; i < getCols()*getRows(); i++) 
     { 
      this->elements.at(i) += rhs.elements.at(i); 
     } 
     return *this; 
    } 

    Matrix& Matrix::operator+(const Matrix& rhs) const 
    { 
     return Matrix(*this) += rhs; 
    } 

但現在矩陣可以有一個類型,我無法確定該矩陣的參考應該是<T>型,會是什麼後果。我應該允許不同的類型相互操作(例如,Matrix <foo> a + Matrix <bar> b有效)?我也有點模糊如何

我對不同類型感興趣的一個原因是爲了方便將來使用複數。我是C++的新手,但很高興潛入我的頭腦中學習。如果您熟悉解決此問題的免費在線資源,我會發現最有幫助的。

編輯:難怪沒有人認爲這是有道理的我身體的所有尖括號被視爲標籤!我無法弄清楚如何逃脫它們,所以我會內聯代碼。

+0

通過從'template <...'行開始,讓你的代碼顯然成爲C++代碼,就可以「逃避」它們。 –

+0

感謝您的提示,但事實並非如此。我正在談論的尖括號是在問題的正常文本主體中。 –

+0

哦,那個。我想這很聰明。通常情況下,你在代碼或變量名稱周圍使用反引號(在你的〜鍵上)來使它們成爲'等寬'。 –

回答

2

我想我應該說明我關於參數化矩陣尺寸的評論,因爲你以前可能沒有看過這種技術。

template<class T, size_t NRows, size_t NCols> 
class Matrix 
{public: 
    Matrix() {} // `data` gets its default constructor, which for simple types 
       // like `float` means uninitialized, just like C. 
    Matrix(const T& initialValue) 
    { // extra braces omitted for brevity. 
     for(size_t i = 0; i < NRows; ++i) 
      for(size_t j = 0; j < NCols; ++j) 
       data[i][j] = initialValue; 
    } 
    template<class U> 
    Matrix(const Matrix<U, NRows, NCols>& original) 
    { 
     for(size_t i = 0; i < NRows; ++i) 
      for(size_t j = 0; j < NCols; ++j) 
       data[i][j] = T(original.data[i][j]); 
    } 

private: 
    T data[NRows][NCols]; 

public: 
    // Matrix copy -- ONLY valid if dimensions match, else compile error. 
    template<class U> 
    const Matrix<T, NRows, NCols>& (const Matrix<U, NRows, NCols>& original) 
    { 
     for(size_t i = 0; i < NRows; ++i) 
      for(size_t j = 0; j < NCols; ++j) 
       data[i][j] = T(original.data[i][j]); 
     return *this; 
    } 

    // Feel the magic: Matrix multiply only compiles if all dimensions 
    // are correct. 
    template<class U, size_t NOutCols> 
    Matrix<T, NRows, NOutCols> Matrix::operator*(
     const Matrix<T, NCols, NOutCols>& rhs) const 
    { 
     Matrix<T, NRows, NOutCols> result; 
     for(size_t i = 0; i < NRows; ++i) 
      for(size_t j = 0; j < NOutCols; ++j) 
      { 
       T x = data[i][0] * T(original.data[0][j]); 
       for(size_t k = 1; k < NCols; ++k) 
        x += data[i][k] * T(original.data[k][j]); 
       result[i][j] = x; 
      } 
     return result; 
    } 

}; 

所以,你會申報的float個2×4矩陣,初始化爲1.0,如:

Matrix<float, 2, 4> testArray(1.0); 

注意,沒有用於存儲是在堆(即使用operator new沒有要求),因爲大小是固定的。你可以在堆棧上分配它。

您可以創建int是另一個夫婦矩陣:

Matrix<int, 2, 4> testArrayIntA(2); 
Matrix<int, 4, 2> testArrayIntB(100); 

複印時,雖然類型不維度必須匹配:

Matrix<float, 2, 4> testArray2(testArrayIntA); // works 
Matrix<float, 2, 4> testArray3(testArrayIntB); // compile error 
// No implementation for mismatched dimensions. 

testArray = testArrayIntA; // works 
testArray = testArrayIntB; // compile error, same reason 

乘法必須有正確的尺寸:

Matrix<float, 2, 2> testArrayMult(testArray * testArrayIntB); // works 
Matrix<float, 4, 4> testArrayMult2(testArray * testArrayIntB); // compile error 
Matrix<float, 4, 4> testArrayMult2(testArrayIntB * testArray); // works 

請注意,如果有一個botch,它在編譯時被捕獲。但是,只有在編譯時固定矩陣尺寸的情況下才可能。還要注意,這個邊界檢查結果在沒有額外的運行時代碼。如果您只是將維數設爲常量,您將得到相同的代碼。

調整大小

如果你不知道在編譯時你的矩陣尺寸,但必須等到運行時,該代碼可能沒有多少用處。您必須編寫一個內部存儲維度的類和一個指向實際數據的指針,並且它需要在運行時執行所有操作。提示:編寫您的operator []將矩陣視爲重塑的1xN或Nx1向量,並使用operator()來執行多索引訪問。這是因爲operator []只能帶一個參數,但operator()沒有這個限制。通過嘗試支持M[x][y]語法,很容易在腳中自我拍攝(強制優化器放棄,至少)。

這就是說,如果有某種你做的調整一個Matrix到另一個,因爲所有尺寸都在編譯時已知的標準矩陣大小調整的,那麼你可以寫一個函數來完成調整大小。例如,該模板函數將重塑任何Matrix成列向量:

template<class T, size_t NRows, size_t NCols> 
Matrix<T, NRows * NCols, 1> column_vector(const Matrix<T, NRows, NCols>& original) 
{ Matrix<T, NRows * NCols, 1> result; 

    for(size_t i = 0; i < NRows; ++i) 
     for(size_t j = 0; j < NCols; ++j) 
      result.data[i * NCols + j][0] = original.data[i][j]; 

    // Or use the following if you want to be sure things are really optimized. 
    /*for(size_t i = 0; i < NRows * NCols; ++i) 
     static_cast<T*>(result.data)[i] = static_cast<T*>(original.data)[i]; 
    */ 
    // (It could be reinterpret_cast instead of static_cast. I haven't tested 
    // this. Note that the optimizer may be smart enough to generate the same 
    // code for both versions. Test yours to be sure; if they generate the 
    // same code, prefer the more legible earlier version.) 

    return result; 
} 

...好,我覺得這是一個列向量,反正。希望很明顯如何解決它,如果不是。無論如何,優化器會看到你正在返回result並刪除額外的複製操作,基本上構造調用者想要看到它的結果。

編譯時間維度狀態檢查

說,我們希望編譯器將停止,如果尺寸爲0(通常造成空Matrix)。有一個我聽說過所謂的「編譯時斷言」把戲,它使用模板專業化,被聲明爲:

template<bool Test> struct compiler_assert; 
template<> struct compiler_assert<true> {}; 

這樣做是什麼讓你寫的代碼,如:

private: 
    static const compiler_assert<(NRows > 0)> test_row_count; 
    static const compiler_assert<(NCols > 0)> test_col_count; 

基本想法是,如果條件是true,模板變成空的struct,沒有人使用,並被默默丟棄。但是,如果它false,編譯器無法找到struct compiler_assert<false>一個定義(只是聲明,這是不夠的),並出現了錯誤。

更好的是安德烈Alexandrescu的的版本(從his book),它可以讓你使用斷言對象的申報名稱爲即興錯誤消息:

template<bool> struct CompileTimeChecker 
{ CompileTimeChecker(...); }; 
template<> struct CompileTimeChecker<false> {}; 
#define STATIC_CHECK(expr, msg) { class ERROR_##msg {}; \ 
    (void)sizeof(CompileTimeChecker<(expr)>(ERROR_##msg())); } 

您填寫什麼爲msg必須是有效的標識符(只有字母,數字和下劃線),但這沒什麼大不了的。然後,我們只需更換使用默認的構造函數:

Matrix() 
{ // `data` gets its default constructor, which for simple types 
    // like `float` means uninitialized, just like C. 
    STATIC_CHECK(NRows > 0, NRows_Is_Zero); 
    STATIC_CHECK(NCols > 0, NCols_Is_Zero); 
} 

瞧,如果我們錯誤地設定尺寸0的一個編譯器停止。有關它的工作方式,請參閱Andrei's book的第25頁。請注意,在true的情況下,只要測試沒有副作用,生成的代碼就會被丟棄,所以沒有膨脹。

+0

非常感謝這個寫得很好的例子。我以前從來沒有見過。只是想知道,這是否讓Matrix無法調整大小?或者你只需​​要使用一些我不知道的C++ foo? –

+0

這意味着Matrix的給定*實例*具有固定的大小。這是您讓編譯器進行優化所付出的代價。順便說一句,有一個稱爲「模板專業化」的功能,可以讓您編寫代碼以用於此模板的某些版本。例如,如果您爲4x4浮點矩陣優化了彙編代碼(例如SSE3),則可以讓編譯器在該代碼中替換「Matrix '的實例。否則,它將使用上面的模板代碼。它非常靈活。 –

1

我不確定我明白你在問什麼。

但我會指出你的運算符聲明不正確和/或不完整。

首先,賦值運算符應返回與其參數相同的類型;即:

const Matrix & operator =(const Matrix & src);

其次,二元運算符返回一個新的對象,所以你不能返回引用。所有的二元運算,因此應將聲明:

Matrix operator+(const Matrix& other) const; // matrix addition 
Matrix operator-(const Matrix& other) const; // matrix subtracton 
Matrix operator&(const Matrix& other) const; // elem by elem product 
Matrix operator*(const Matrix& other) const; // matrix product 

事實上,它被認爲是更好的風格聲明和實現二元運營商爲全球的朋友,而不是功能:

class Matrix { ... }; 

inline Matrix operator+(const Matrix& lhs,const Matrix& rhs) 
{ return Matrix(lhs)+=rhs; } 

希望這有助於。


現在我明白你在問什麼了。

在這種情況下,大概你的各種操作符的實現將包含對複合類型的操作。那麼Matrix對Matrix是否有意義的問題呢,取決於string op int是否有意義(以及這樣的事情是否有用)。您還需要確定返回類型可能是什麼。

假設返回類型是一樣的LHS操作數的聲明看起來是這樣的:

template <typename T> 
class Matrix 
{ 
    template <typename U> 
    Matrix<T>& operator+=(const Matrix<U>& rhs); 
}; 

template <typename T,typename U> 
Matrix<T> operator+(const Matrix<T>& lhs,const Matrix<U>& rhs) 
{ return Matrix<T>(lhs)+=rhs; } 
+0

'常量矩陣運算符=(常量矩陣& src);'將工作,但返回一個非const引用是普遍接受的賦值運算。對於一個例子,檢查出[SGI的字符串實現](http://www.sgi.com/高科技/ STL/basic_string.html) –

+0

哎呀,複製粘貼背叛了我,我知道,二進制文件沒有返回參考我想知道的是如何讓一個變量的類型會影響到運營商例如,如果有人做了什麼。矩陣,並試圖添加一個矩陣?我應該嘗試通過矩陣運算符+(常量矩陣及其他)來阻止該問題嗎? –

0

首先,拷貝賦值操作符不應該有const Matrix&作爲它的返回類型;你的界面是正確的。

格蘭特關於如何實現二元運算符的建議是做這些事情的普遍接受的方式。

這是一個很好的練習,但很快就會看到爲什麼在C++中使用線性代數是一個不好的主意。像A+BA*B這樣的操作僅在矩陣的維數匹配時纔有效。

+0

我不知道爲什麼邊界檢查是一個大問題。 。 –

+0

邊界檢查,如線程安全,最好通過策略類來實現。此外,如果維度設置爲通過模板參數編譯時間,而不是像'std :: vector'那樣將其留在運行時,然後可以在編譯時檢查邊界檢查和運算符兼容性,並且生成的目標代碼將具有與硬編碼相同的性能。這真的很酷。 –

0

根本不需要添加太多內容,因爲在模板中,類名稱本身引用了當前的模板參數。所以下面是等效的:

template <typename T> struct Foo 
{ 
    Foo<T> bar(const Foo<T> &); 
    Foo bar2(const Foo *);  // same 
}; 

所以你所有的操作只是沒有改變。你應該補充的是,轉換一個矩陣型到另一個構造函數:

temlate <typename T> class Matrix 
{ 
    template <typename U> Matrix(const Matrix<U> &); // construct from another matrix 
    /*...*/ 
}; 

使用轉換構造函數,你可以在運營商混合矩陣,如Matrix<T>::operator+(Matrix<U>)將使用轉換創建Matrix<T>類型的參數,並那麼你使用你已經實現的操作符。

在C++ 11中,您可以將static_assert(std::is_convertible<U, T>::value, "Boo");添加到您的轉換構造函數中,如果您使用不兼容的類型調用它,將爲您提供有用的編譯時診斷。

+1

另外,我不確定是否允許從另一個Matrix類型進行隱式轉換將被認爲是良好的做法。我認爲另一種矩陣類型的構造是「明確的」,以便在涉及不同矩陣類型的表達式中不會有任何意外。 –

+0

@艾米爾:是的,好主意,絕對值得考慮。 –

1
Matrix<double> x = ...; 
Matrix<int> y = ...; 
cout << x + y << endl; // prints a Matrix<double>? 

好的,這是可行的,但問題很快就會變得棘手。

Matrix<double> x = ... 
Matrix<complex<float>> y = ... 
cout << x + y << endl; // Matrix<complex<double>>? 

,如果你需要,你的二元操作使用像型操作數和迫使你的應用程序構建,明確你將最有可能是最幸福的類型轉換它們的值。對於後一種情況:

cout << ((Matrix<complex<double>>) x) + ((Matrix<complex<double>>) y) << endl; 

您可以提供成員模板構造函數(或類型轉換運算符)來支持轉換。

template <typename T> 
class Matrix { 
    ... 
public: 
    template <typename U> 
    Matrix(const Matrix<U>& that) { 
     // initialize this by performing U->T conversions for each element in that 
    } 
    ... 
}; 

另一種選擇,讓您的二元運算模板推導基於元素類型的兩個操作數的正確矩陣返回類型,需要一些比較複雜的模板元編程,你正在尋找進入可能不是什麼。

+0

並改變運營商看起來像這樣:矩陣&運算符+ =(常量矩陣和其他);會做的伎倆? –

+0

@user:我認爲最好是轉換構造函數是'explicit',並且用戶調用轉換構造函數而不是轉換。例如:'cout << Matrix >(x)+ Matrix >(y)<< endl;'允許隱式轉換會導致難以追蹤的bug。 –

+0

...這就是爲什麼我認爲語言應該被設計爲默認情況下構造函數是顯式的,並且您需要添加一個「隱式」關鍵字以允許隱式轉換。 –