1

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

示例 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 引擎上下文中没有任何逻辑意义(结果是一个不代表任何有效转换的矩阵)。

在我检查过的几乎所有库中的不同类型上都可以找到类似的示例。我在 Alexander Stepanov 的 Elements of Programming 中读到,当它们没有意义时,不应更改运算符的含义或实施它们,但我看到了许多不遵守这些准则的示例。

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

编辑:

我会试着举一个更好的例子:

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 元素数组的其他东西)上下文中这是什么意思?

4

3 回答 3

7

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

通常,矩阵加法是定义明确的数学运算,因此您关于“不尊重数学要求”的观点是完全错误的。矩阵乘法 不是可交换的,所以你也不应该期望它。

至于实际使用

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

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

于 2013-08-07T13:34:19.053 回答
5

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

但是,您的问题并不清楚这些重载真的没有意义:相反,它们似乎对您的特定用例没有意义。

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

现在,如果一个库提供了一个通用矩阵,并且您只希望它用于转换,那么库提供一个 TransformationMatrix 可能是合理的,它只提供对转换来说是合理的矩阵操作的子集。确实,这听起来是个不错的主意,尽管它可能会以库的类型系统中相当多的额外复杂性为代价。

于 2013-08-07T13:40:38.990 回答
0

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

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

由于 mat4x4 是一种模板类型,有什么可以阻止您存储指向函数调用或函数指针的指针?

您可以说一个 4x4 矩阵,其中每个索引中保存了一个函数指针,并且该矩阵充当对函数指针的引用,以在多个线程中并行执行相同的工作。然后有一个带有另一组函数指针的第二个 4x4 矩阵将以相同的方式运行。重载的 operator+() 在这里有什么用处?

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

假设您有一个大小为 wxh 的纹理,每个像素值代表 3D 地形上照明的正常值。然后,我们要对每个像素数据执行一次操作 -->(正常值),然后以确切的顺序依次执行另一次操作。假设这个函数指针 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,这是数据类型的一个子集。由于这些操作是在逐个像素的基础上完成的,因此这比运行双 for 循环并调用一个方法、返回并执行另一个双 for 循环或执行双 for 循环并在数据集。

这个 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 );    
};

通过模板特化,第二个类型名可以接受的唯一类型是指向函数调用的指针。在这种情况下,传入的类型 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 的结构。

于 2015-04-08T07:23:10.273 回答