我想我应该说明我关于参数化矩阵尺寸的评论,因为你以前可能没有看过这种技术。
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++代码,就可以“逃避”它们。 –
感谢您的提示,但事实并非如此。我正在谈论的尖括号是在问题的正常文本主体中。 –
哦,那个。我想这很聪明。通常情况下,你在代码或变量名称周围使用反引号(在你的〜键上)来使它们成为'等宽'。 –