首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简单矩阵类C++14

简单矩阵类C++14
EN

Code Review用户
提问于 2016-08-01 13:11:18
回答 1查看 1.6K关注 0票数 4

我创建了一个简单的4x4矩阵类(列-主)。我希望它是高效的,并使用C++14's的全部能力。我能改进一下吗?

代码语言:javascript
复制
#include <array>

class mat4
{
public:
    constexpr mat4() noexcept : matrix() {}

    constexpr mat4(const std::array<float, 16> &m) noexcept : matrix(m) {}
    constexpr mat4(std::array<float, 16> &&m) noexcept : matrix(std::move(m)) {}

    constexpr mat4(const mat4 &other) noexcept : matrix(other.matrix) {}
    constexpr mat4(mat4 &&other) noexcept : matrix(std::move(other.matrix)) {}

    constexpr bool operator==(const mat4 &other) const noexcept
    {
        for (size_t i = 0; i < 16; ++i) {
            if ((*this)[i] != other[i]) {
                return false;
            }
        }

        return true;
    }

    constexpr bool operator!=(const mat4 &other) const noexcept
    {
        return !(this->operator==(other));
    }

    constexpr mat4& operator+=(const mat4 &other) noexcept
    {
        for (size_t i = 0; i < 16; ++i) {
            (*this)[i] += other[i];
        }
        return *this;
    }

    constexpr mat4& operator*=(float scalar) noexcept 
    {
        for (size_t i = 0; i < 16; ++i) {
            (*this)[i] *= scalar;
        }
        return *this;
    }

    mat4& operator=(mat4 other) noexcept
    {
        std::swap(this->matrix, other.matrix);
        return *this;
    }   

    constexpr float& operator[](size_t index) { return const_cast<float&>(static_cast<const std::array<float, 16>&>(matrix)[index]); }
    constexpr float operator[](size_t index) const { return matrix[index]; }

    void print() const noexcept
    {
        printf("\n");
        printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[0], matrix[4], matrix[8], matrix[12]);
        printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[1], matrix[5], matrix[9], matrix[13]);
        printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[2], matrix[6], matrix[10], matrix[14]);
        printf("[%.2f][%.2f][%.2f][%.2f]\n", matrix[3], matrix[7], matrix[11], matrix[15]);
    }

private:
    std::array<float, 16> matrix;
};

constexpr const mat4 operator+(mat4 lhs, const mat4 &rhs) noexcept
{
    lhs += rhs;
    return lhs;
}

constexpr const mat4 operator*(const mat4 &lhs, const mat4 &rhs) noexcept
{
    mat4 result;

    for (size_t i = 0; i < 4; ++i) 
    {
        for (size_t j = 0; j < 4; ++j)
        {
            for (size_t k = 0; k < 4; ++k) {
                result[i + 4 * j] += lhs[i + 4 * k] * rhs[k + 4 * j];
            }
        }
    }

    return result;
}

constexpr const mat4 operator*(mat4 lhs, float scalar) noexcept 
{
    lhs *= scalar;
    return lhs;
}

您可以在这个现场演示中测试它。

EN

回答 1

Code Review用户

发布于 2016-08-05 14:28:28

这是试图回答我自己的问题。如果有人能对此发表评论,我将不胜感激--他们同意/不同意我的部分解决办法。

  • mat4类型的第一个参数应该通过引用传递,而不是通过operator+operator* (标量版本)和operator=签名中的值传递。

在这个参考文献的帮助下,让我们对函数operator+进行分析(就像在本文章中所做的那样),以理解为什么(对operator*operator=的分析是相似的)。

首先,我们可以在上面的代码中假设两件事:

  1. 当复制省略的条件满足时,编译器将省略复制或移动mat4的构造函数。我在GCC 6.1和Clang 3.8下测试了这一点,在我使用这个类的操作符的所有情况下都是这样。
  2. 对于这个类mat4,默认构造函数>=复制构造函数= move构造函数(执行速度越快)。因为std::array是一个聚合,所以移动一个float并不比复制它更快。复制/移动构造函数可能在内部使用类似memcpy和默认构造函数memset (因为mat4's构造函数中的调用matrix() )将所有元素设置为0(我怀疑这会比memcpy慢,甚至可能更快)。

在调用operator+时,有两种情况需要考虑:

  1. 传递给函数的类型mat4的第一个参数是lvalue。
  2. 这是一种价值。

如果第一个参数是lvalue,则传递值(当前)版本的operator+将调用mat4的复制构造函数将该对象复制到lhs中。然后,当函数返回时,它将调用mat4的move构造函数将lhs移动到operator+返回的对象中。调用移动而不是复制构造函数的原因在上面给出的引用中解释了。以下是有趣的摘录:

(自C++11)在返回语句或抛出表达式中,如果编译器不能执行复制省略,但复制省略的条件满足或将满足,除非源是函数参数,编译器将尝试使用移动构造函数,即使对象是由lvalue指定的;有关详细信息,请参阅返回语句。

但是,如果传递给operator+的参数是rvalue,则将省略将该值复制到lhs中。在这种情况下,只调用移动构造函数。

现在,让我们对operator+的按引用版本进行完全相同的分析,我建议实现如下:

代码语言:javascript
复制
constexpr const mat4 operator+(const mat4 &lhs, const mat4 &rhs) noexcept
{
    mat4 result;
    for (size_t i = 0; i < 16; ++i) {
        result[i] = lhs[i] + rhs[i];
    }
    return result;
}

在这两种情况下,即当函数的第一个参数为lvalue或rvalue时,调用函数只会导致默认构造函数(mat4 result;)的单个调用。注意,临时对象result不会被移动,也不会复制到函数的返回值中,因为复制省略的条件是满足的。

结论:不可否认,最后一种解决方案速度更快。请注意,并不是每次都是这样的,只因为operator+是如何实现的,并且由于两个假设,特别是第二个假设,即默认构造函数>=复制构造函数= move构造函数(注意,如果默认构造函数=复制构造函数=移动构造函数,这个结论也是正确的)。

  • 在operator=中,没有必要使用std::swap,因为您做了不必要的复制。只需使用this->matrix = other.matrix;。只有当类实现了一个资源时,std::swap才会很有趣(参见这里)。
  • 在完成constexprstd::array (C++17)的支持之前,您可以创建自己的数组包装器,这样就不必用operator[]编写那种难看的强制转换了

下面是一个受std::array启发的包装器,可以工作:

代码语言:javascript
复制
template<class _Ty, size_t _Size>
struct arr
{
    constexpr _Ty& operator[](size_t index) noexcept
    {
        return _arr[index];
    }
    constexpr const _Ty& operator[](size_t index) const noexcept
    {
        return _arr[index];
    }

    constexpr _Ty *data() noexcept
    {
        return _arr;
    }

    constexpr const _Ty *data() const noexcept
    {   
        return _arr;
    }

    float _arr[_Size];
};
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/136541

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档