我想我應該說明我關於參數化矩陣尺寸的評論,因爲你以前可能沒有看過這種技術。
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
的情況下,只要測試沒有副作用,生成的代碼就會被丟棄,所以沒有膨脹。
通過從'template <...'行開始,讓你的代碼顯然成爲C++代碼,就可以「逃避」它們。 –
感謝您的提示,但事實並非如此。我正在談論的尖括號是在問題的正常文本主體中。 –
哦,那個。我想這很聰明。通常情況下,你在代碼或變量名稱周圍使用反引號(在你的〜鍵上)來使它們成爲'等寬'。 –