游戏开发论坛

 找回密码
 立即注册
搜索
查看: 7290|回复: 6

面向对象带来的的“陷阱”-- 以矩阵乘法为例

[复制链接]

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
发表于 2008-3-19 16:05:00 | 显示全部楼层 |阅读模式
这里展示了一个例子问题,注意,只不过是一个例子。
问题是:“如何计算矩阵乘法:A = A * B ”。
目的当然不是计算矩阵乘法,所以你有必要把它想象成一个大型项目的系统。

如果“运算过程”不面向对象,通常是这么做的:

#include "inst3DMath.h"

void Mat44MulMat44(MAT44 *ret, const MAT44 *mat1, const MAT44 *mat2)
{
    ZeroMemory(ret,sizeof(MAT44));
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
            for(Int32 s=0;s<=3;s++)
                ret->m[j] += mat1->m * mat2->m[j];
}
Mat44MulMat44( &result, &A, &B );
Copy( &A, &result );

或者,可读性更高的,可以写成:

void Mat44MulMat44(MAT44 *ret, const MAT44 *mat1, const MAT44 *mat2)
{
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
        {
            Float tmp = 0;

            for(Int32 s=0;s<=3;s++)
                tmp += mat1->m * mat2->m[j];

            ret[j] = tmp;
        }
}
Mat44MulMat44( &result, &A, &B );
Copy( &A, &result );

但是,如果像下面这么做,就错了:

void Mat44MulMat44(MAT44 *mat1, const MAT44 *mat2)
{
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
        {
            Float tmp = 0;

            for(Int32 s=0;s<=3;s++)
                tmp += mat1->m * mat2->m[j];

            mat1[j] = tmp;
        }
}
Mat44MulMat44( &A, &B );

-------------------------------------------------------------------------
“这当然!楼主当我是XX吗!?原来楼主就是要给我看这种垃圾帖子啊!”
----你这么抱怨说。。。
-------------------------------------------------------------------------

现在让我们想象,这是一个大型项目中的复杂系统,每个矩阵元素都是一个小系统,
每个元素,都有自己的属性,方法。。。。这看起来多么美妙。。。。

#include <instDefines.h>

class CElement:virtual public IDynamic
{
private:
    Int32 m_i,m_j; //记录自己的编号!“有效利用了面向对象的封装”
    Float m_Value;
public: //你可以无视这两行
    virtual void *GetAddr()const{ return (void *)this; }
    virtual CStr GetClass()const{ return L"CElement"; }
public:
    Float Read()const{ return m_Value; }
    void DoSomething( Int32 j );
};

现在,名称有所改变,那个函数改成了2个函数,“DoSystem”和“DoSomething”,
矩阵改成了"system1,2"。。。因为这是一个大型项目的复杂系统。。。

CElement * * *system1, * * *system2; // 直接用Global - 简单起见

void DoSystem()
{
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
        {
            system1[j] -> DoSomething(); //需要让系统1的元素[j]做一个事情。
        }
}

void CElement:oSomething()
{
    Float tmp = 0;

    for(Int32 s=0;s<=3;s++)
        tmp += system1[m_i]->Read() * system2[m_j]->Read();

    m_Value = tmp ;
}

现在我们调用 DoSystem();
然后,程序并没有获得你想要的那个结果,事实上,
它等于就是崩溃了。。。

在一个大型项目的测试中,你会很讨厌这种错误,并且硬编码来修正它。

参考:http://bbs.gameres.com/showthread.asp?threadid=106038

请警惕这种错误吧!不要被面向对象所迷惑!

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-3-19 19:16:00 | 显示全部楼层

Re:面向对象带来的的“陷阱”-- 以矩阵乘法为例

在一个矩阵运算中,我们可以用一个临时变量result来解决这个问题。

但是,在大型系统中,我们不可能使用这种方法。

这里介绍几个方法:

(一)单线程 + Update()

class CElement:virtual public IUpdatable
{
private:
    Int32 m_i,m_j;
    Float m_next, m_value;
public: //你可以无视这两行
    virtual void *GetAddr()const{ return (void *)this; }
    virtual CStr GetClass()const{ return L"CElement"; }
public:
    virtual void *Update(DWORD dt);
public:
    Float Read()const{ return m_value; }
    void DoSomething( Int32 j );
};

CElement * * *system1, * * *system2; // 直接用Global - 简单起见

void DoSystem()
{
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
        {
            system1[j] -> DoSomething(); //需要让系统1的元素[j]做一个事情。
        }
}

void CElement:oSomething()
{
    Float tmp = 0;

    for(Int32 s=0;s<=3;s++)
        tmp += system1[m_i]->Read() * system2[m_j]->Read();

    m_next = tmp;
}

void CElement::Update(DWORD dt)
{
    m_next = ( m_value = m_next );
    // 其他处理...
}

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-3-19 19:56:00 | 显示全部楼层

Re:面向对象带来的的“陷阱”-- 以矩阵乘法为例

(二)“APRAM”计算模式 -- 多线程,或者并行计算

这里需要用2个phase.

在 phase1 中,计算tmp,
在 phase2 中,写入m_value.

phase1 和 phase2 之间是一个障碍同步。

从逻辑上说,在软件层面上,我们需要 N 个线程(处理单元),
不论具体硬件,操作系统是什么,不论是单CPU还是多个,
在设计思想上,都是一样的。

只不过,当CPU数目少于需要的线程(处理单元)的数目时,
windows操作系统会为我们“模拟并行执行”。
但这种“模拟”,对于应用程序来说,是透明的。


这里就不给出实际代码了,只给出伪代码。


class CElement:virtual IRunnable
{
private:
    Int32 m_i,m_j;
    Float m_value;
public: //你可以无视这两行
    virtual void *GetAddr()const{ return (void *)this; }
    virtual CStr GetClass()const{ return L"CElement"; }
public:
    virtual void Run(); //线程(处理单元)入口
public:
    Float Read()const{ return m_value; }
    void DoSomething( Int32 j );
};

CElement * * *system1, * * *system2; // 直接用Global - 简单起见

void DoSystem()
{
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
        {
            system1[j] -> DoSomething(); //需要让系统1的元素[j]做一个事情。
        }
}

void CElement:oSomething()
{
    Float tmp = 0;

    for(Int32 s=0;s<=3;s++)
        tmp += system1[m_i]->Read() * system2[m_j]->Read();

    <Hold>(); //障碍同步,程序将停止在这里,等待其他线程(处理单元),
              //直到所有线程(处理单元)都调用了<Hold>(); (伪代码)

    m_value = tmp;
}

void CElement::Run() //线程(处理单元)入口
{
    DoSystem();
}


OK,现在我们启动所有的线程(处理单元),平台任意,
可以是win32多线程环境,或者并行计算机

你会发现,这个系统的确可以正常工作。。。

如果你拥有一个并行计算机,
这个矩阵运算,就可以被高速地并行处理,这不酷吗!?

149

主题

4981

帖子

5033

积分

论坛元老

Rank: 8Rank: 8

积分
5033
QQ
发表于 2008-3-22 00:50:00 | 显示全部楼层

Re:面向对象带来的的“陷阱”-- 以矩阵乘法为例

void Mat44MulMat44(MAT44 *ret, const MAT44 *mat1, const MAT44 *mat2)
{
    ZeroMemory(ret,sizeof(MAT44));
    for(Int32 i=0;i<=3;i++)
        for(Int32 j=0;j<=3;j++)
            for(Int32 s=0;s<=3;s++)
                ret->m[j] += mat1->m * mat2->m[j];
}

呃……那个……如果这里的ret与mat1是同一个矩阵呢?

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-3-22 23:23:00 | 显示全部楼层

Re:面向对象带来的的“陷阱”-- 以矩阵乘法为例

回复楼上的:

1 本贴不是讨论矩阵运算方法本身的帖子,只是以此为例子。
2 这需要用户自己负责,如果ret == mat1

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2008-3-29 13:14:00 | 显示全部楼层

Re:面向对象带来的的“陷阱”-- 以矩阵乘法为例

前几天看到MSDN上的文章:
http://msdn2.microsoft.com/zh-cn/magazine/cc337885.aspx?rss_fdn=MSDNTopNewInfo

仔细琢磨,这很有道理,
如果2个事物需要相互依赖,那么就不要设计为2个类,
需要设计为3个类,第3个类用来处理他们之间的耦合。

不过这种设计的话,有些情况,会令你觉得“这简直不是面向对象!”
就好比MS的一些类库,典型的D3D9,看起来不那么“面向对象”,
比如,IDirect3DDecive9:rawPrimitive();
咱们往往觉得,应该是:“IDirect3DVertexBuffer::Render()”

以前感觉MS的类库好像“根本不面向对象”,
实际上MS是非常正确的,他避免了复杂的耦合。

不过,极端情况,假设你需要编写一个电路系统,
实际上,电路中的每个元件之间都是耦合的,
然后,你根本不能在每个元件的类中定义什么“方法”,
所有的操作几乎都会放在一个“总管”一样的类中进行。
这样,实际上,代码就退化为,面向过程式。。。

362

主题

3023

帖子

3553

积分

论坛元老

Rank: 8Rank: 8

积分
3553
 楼主| 发表于 2017-8-27 20:40:44 来自手机 | 显示全部楼层
http://bbs.gameres.com/forum.php?mod=viewthread&tid=663392&mobile=yes解决面向对象的陷阱
(simulation模版库)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-2-24 18:06

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表