C ++运算符重载不符合数学要求

时间:2013-08-07 13:31:42

标签: c++ operator-overloading

我必须编写一个供内部使用的数学库。我开始从开源库中看到不同的实现,我在运算符重载上发现了一些奇怪的东西 - 它们不尊重数学/逻辑要求

示例1:Irrlight矩阵(http://irrlicht.sourceforge.net/docu/matrix4_8h_source.html

  • operator*因非交换操作而超载
  • operator+/-/+=/-=被重载,但它们在3D引擎上下文中没有任何逻辑含义(结果是一个不代表任何有效转换的矩阵)。

示例2:GLM矩阵(http://glm.g-truc.net/

  • operator++/--被重载,但它们在3D引擎上下文中没有任何逻辑含义(结果是一个不代表任何有效转换的矩阵)。
  • operator+=/-=被重载,但它们在3D引擎上下文中没有任何逻辑含义(结果是一个不代表任何有效转换的矩阵)。

在我检查的几乎所有库中,可以在不同类型上找到类似的示例。 我读过亚历山大·斯捷潘诺夫的“编程元素”,他们不应该改变运算符意义,也不应该在没有意义的情况下实现它们,但我看到很多例子都没有遵守这些指导原则。

这是一个好习惯吗?如果是的话,请你给我一些论据。如果不是,为什么每个人都这样做?

编辑:

我会尝试一个更好的例子:

template <typename U>
GLM_FUNC_DECL tvec4<T> & operator*=(tvec4<U> const & v);

使用此实现

template <typename T>
template <typename U> 
GLM_FUNC_QUALIFIER tvec4<T> & tvec4<T>::operator*=
(
    tvec4<U> const & v
)
{
    this->x *= T(v.x);
    this->y *= T(v.y);
    this->z *= T(v.z);
    this->w *= T(v.w);
    return *this;
}

你能解释一下这在数学向量的背景下是什么意思(不是颜色或点或......可以表示为4个元素数组的其他东西)吗?

3 个答案:

答案 0 :(得分:7)

是的,这些运营商是完全合理的。

一般来说,matrix addition是明确定义的数学运算,所以你关于“不尊重数学要求”的观点是完全错误的。 Matrix multiplication 不是可交换的,所以你也不应该期待。

至于实际用途

我不知道Irrlicht,但在GLM中,这是因为矩阵不仅仅用于渲染。

GLM类型基于GLSL矩阵建模;这些可以存储3D变换的事实是无关紧要的,因为着色器可能会使用它们来存储任意数据。然后,加法和减法可以是人们可能使用它们的有效操作,“3D引擎上下文”只是一个可能的上下文。

答案 1 :(得分:5)

当然,以真正无意义的方式重载运算符是不好的做法。

然而,从您的问题中可以清楚地看出,这些重载确实有意义:相反,它似乎对您的特定用途没有意义情况下

一个比你需要的更通用的库不是一个bug,而且库通常在普遍性方面是错误的。

现在,如果一个库提供了一个通用的Matrix,并且你只希望它用于转换,那么库提供一个只提供矩阵运算子集的TransformationMatrix可能是合理的。理智转型。实际上,这听起来是个不错的主意,尽管它可能以图书馆类型系统中相当大的额外复杂性为代价。

答案 2 :(得分:0)

拥有通用数学库是一回事,但是对于glm数学库,它的设计考虑了GLSL的惯例。它是这样设置的,这样当您在GLSL中编写着色器时,您已经熟悉了glm数学库的使用,并且工作流程更加平滑。

在硬件和CPU上使用着色器和渲染时,所有数据集都存储在视频卡的ram中。矩阵不仅用于进行基本的数学变换,例如平移,旋转,缩放,倾斜等,它们也可以用于做其他事情。当您使用现代视频卡上的着色器进行编程时,由于其架构,它们在并行过程中非常有效。

由于mat4x4是一个模板类型,是什么阻止你存储指向函数调用或函数指针的指针?

你可以说一个4x4矩阵,其中有一个函数指针保存到每个索引中,矩阵充当对函数指针的引用,以便在并行时在多个线程中执行相同的工作。然后使用另一组函数指针的第二个4x4矩阵将以相同的方式起作用。在这里使用重载运算符+()会有什么用?

使用mat4x4 A + mat4x4 B并不等于你期望的mat4x4 C.不是在矩阵中添加每个元素的数据来为您提供一个新矩阵,您将定义的重载运算符将允许您在A离开范围后立即调用B中的函数指针。允许您在特定数据集上连接多个函数调用,同时在多个线程中工作。

我们假设你有一个大小为w x h的纹理,每个像素值代表3D地形上的照明的正常值。然后,我们想对每个像素数据执行一个操作 - &gt; (正常值),然后按照确切的顺序彼此相继。让我们说这个函数指针foo()对每个法线执行一组操作,然后函数指针goo()对这些法线执行不同的操作,以获得所需的结果。

因此,矩阵A有16个单元格,每个单元格表示对foo()函数指针的引用,矩阵B对goo()执行相同的操作。现在,对于引擎中的这个实现,设计者必须编写自己的重载operator +()函数,以便在纹理T(普通数据)上的并行线程中调用foo(),然后立即调用它该函数并行调用T上的goo(),并可能返回一个向量或char [x * h],并返回着色器的结果。

这将有16个线程,每个线程处理在同一数据集上工作的相同操作。对于T(正常值)中的每个像素条目,即W x H;每次调用foo和goo都会对(W x H)/ 16(数据类型的子集)起作用。由于这些操作是逐个像素地完成的,因此比通过双循环运行并调用一个方法要快得多,返回并执行另一个双循环或执行双循环并在一个接一个地调用一个方法数据集。

此Mat4x4的返回类型不是您所期望的转换,而是一个并行使用两个函数调用的数据集。现在在这个例子中,重载的操作符必须由程序员编写,因为它不完全包含在库中。

大多数库都设计为松散的通用和通用的,具有您期望的所有常用功能。没有图书馆是完美的,有些比其他图书馆更好。每个图书馆都有自己的优势和缺点。

在上面的示例中,您可能无法直接从提供的mat4x4类中执行此操作,但在创建此类引擎的源代码时,您可以继承glm :: mat4x4类并创建自己的类。看起来可能是这个

namespace glm {
template<typename T>
class mat4x4 {

};
} // namespace


// In your class definition you might have
#include <glm/mat4x4.hpp>

template<typename T, typename FuncPtr>
class myMat4x4 : public glm::mat4x4 {
private:
    std::vector<T> m_vData;
    FuncPtr        m_funcPtr;
public:
    myMat4x4();
    explicit myMat4x4( std::vector<T>& vData ); // std::vector<T> has the data
    // from texture tex1 that was previously stored.
    const std::vector<T>& operator+( const myMat4x4<T,FuncPtr>& rhs );    
};

通过模板特化,第二个typename可接受的唯一类型是指向函数调用的指针。在这种情况下,传入的类型T必须也匹配FuncPtr接受的方法所接受的类型。在构造函数中,您必须将保存的FuncPtr设置为glm :: mat4x4的每个元素,但是在派生类中。您的重载运算符可能类似于:

template<typename T, typename FuncPtr>
std::vector<T>& myMat4x4<T,FuncPtr>::operator+( const myMat4x4<T,FuncPtr>& rhs ) {
    // Invoke this->m_funcPtr on this->m_vData save results back into
    // this->m_vData where each this[m][n] element is called on m_vData[i]
    // meanining this[0][0] FuncPtr works on m_vData[0] T, this[0][1] FuncPtr works on m_vData[1] ...
    // until this[m][n] works on m_vData[last] then make sure m_vData is updated correctly and valid.
    // Next would be to invoke rhs.m_funcPtr on this->m_vData in the same fashion and save data into this->m_vData.
    // Here the rhs myMat4x4 doesn't have anything saved into its (rhs)m_vData since it used the default constructor.
    // But it does have the pointer to goo() saved in rhs.m_funcPtr
    // Check validity of data set if everything is okay and no errors or exceptions, now we can just return this->m_vData
} 

正如您所看到的,在Matrix类的这个示例中,它没有遵循矩阵运算的数学规则来进行转换。但它是一种在数据集上构建并行多线程操作的方法,这有利于GPU的结构化。