C++ 模板类的运算符

标签 c++ templates data-structures matrix operator-overloading

我正在通过创建自己的数据结构类(准确地说是矩阵)自学 C++,并将其更改为类型为 <T> 的模板类。从只使用 double 。重载矩阵运算符非常标准

    // 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;
    }

但是现在 Matrix 可以有一个类型,我无法确定哪个矩阵引用应该是类型 <T>以及后果是什么。我应该允许不同的类型相互操作吗(例如 Matrix <foo> a + Matrix <bar> b 是有效的)?我也有点不清楚如何

我对不同类型感兴趣的一个原因是为了方便将来使用复数。我是 C++ 的新手,但很高兴能深入学习。如果您熟悉处理此问题的任何免费在线资源,我会发现这些资源最有帮助。

编辑:难怪没人认为这是有道理的,我正文中的所有尖括号都被视为标签!我不知道如何转义它们,所以我将对它们进行内联编码。

最佳答案

我认为我应该说明我对参数化矩阵维度的评论,因为您以前可能没有见过这种技术。

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 的 2x4 矩阵s,初始化为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

请注意,如果出现问题,它会在编译时被捕获。不过,这只有在矩阵维度在编译时固定的情况下才有可能。另请注意,此边界检查不会产生额外的运行时代码。如果您只是将尺寸设置为常量,您将获得相同的代码。

调整大小

如果您在编译时不知道您的矩阵尺寸,但必须等到运行时,此代码可能没有多大用处。您必须编写一个在内部存储维度和指向实际数据的指针的类,并且它需要在运行时完成所有工作。提示:写下你的 operator []将矩阵视为 reshape 的 1xN 或 Nx1 vector ,并使用 operator ()执行多索引访问。这是因为 operator []只能带一个参数,但是operator ()没有这样的限制。尝试支持 M[x][y] 很容易搬起石头砸自己的脚(至少迫使优化器放弃)。语法。

就是说,如果有某种标准矩阵调整大小,您可以调整一个 Matrix 的大小。转换成另一个,假设所有维度在编译时都是已知的,那么您可以编写一个函数来调整大小。例如,此模板函数将 reshape 任何 Matrix。进入列 vector :

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;
}

...好吧,无论如何,我认为这是一个列 vector 。希望如果没有,如何解决它是显而易见的。无论如何,优化器会看到你正在返回 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>定义 (只是一个声明,这还不够)并且会出错。

更好的是 Andrei 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在这种情况下,只要测试没有副作用,生成的代码就会被丢弃,因此不会膨胀。

关于C++ 模板类的运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7265284/

相关文章:

c++ - 具有最低值的查找元素的关联容器

c++ - 从二进制文件中读取 Int

c++ - 消息分配运算符不使用交换

c++ - 'catkin_make' 期间 ROS hydro opencv2 链接错误

c++ - 类型转换模板函数返回值

c++ - 函数指针作为模板参数

c++ - 如何从派生类访问派生基成员?(在 C++ 中)

ruby - 二十一点游戏的数据结构

java - 二维递归

python - Sphinx 自动图像编号/标题?