KeyFC欢迎致辞,点击播放
资源、介绍、历史、Q群等新人必读
KeyFC 社区总索引
如果你找到这个笔记本,请把它邮寄给我们的回忆
KeyFC 漂流瓶传递活动 Since 2011
 

[M] Prelude to K.O. (4)

[ 23370 查看 / 45 回复 ]

回复:[M] Prelude to K.O. (4)

这里的TFunc应该是指针吧.如果不是就会觉得很不可理解TFunc到底是什么结构.
如果是指针,这个值就是不固定的,因为许多类都可能符合这个接口,
而这些接口的地址都是不同的.
C++的函数指针只能指向全局或静态函数,指向某个非虚函数也有方法,
但指向虚函数的指针如何实现是不可想象的.
其实我们为什么只要这个成员函数呢,
C++中没遇到这个问题的原因是我们通常使用基类指针,


那么,请给出一个C++的实现,满足如下使用方法:
1. 线程被包装在一个类中(可以命名WorkerThread);
2. 任何一个对象中符合" int (WorkerThread* Thread); "接口的方法都可以被分配给这个"线程对象";
3, 一旦分配,该线程自动调用该对象的方法,并将本"线程对象"实例的指针传递给该方法。
如果不用"丑陋"的Cast和啰嗦的Template,你会发现这个简单的任务对于C++就是Mission Impossible...

而这些接口的地址都是不同的......但指向虚函数的指针如何实现是不可想象的

Object Pascal的设计者们应该是超人了 :D 因为我前面描述的那个Pascal的定义不仅是指向虚函数的,而且是指向"虚类"的...
其实仔细想想,编译器编译你的程序的时候是拥有你的所有类的VMT的,实现对于这种架空的指针的支持,仅仅是做一些偏移量运算而已。

其实,你也不能怪C++的设计者们想不到,C++没有这个东西是因为它的设计理念不同。
C/C++不是一个Strong Type的语言,因此对类型的要求很宽松。
起因要追溯历史到C的诞生了。发明C的Unix编程者们都是Programming Guru,他们的信仰是什么?
"Type checking is for weak minds" (类型检查是为"脑容量有限者"准备的) :D
因此,通过前面我提到的Cast的方法能够实现的操作,C++就肯定不会增加另外一种带类型的方法来解决。

其实,M$的C++里面是有这样的架空指针的,不过有足够的理由相信,这个语言扩展出现在Delphi首席设计师被挖去M$之后... :D
因此可以看出语言总是相互借鉴发展的,因此我告诉你下面的事实你也不要感到意外:

话说回来,"重载操作符"好像只是C++的特性吧-_-|


至少2年前,Delphi的Object Pascal中就增加了操作符重载的支持。
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

大致是这样实现的,增加一个共同的接口即可.

class WorkerThread;

class InterfaceA
{
public:
  virtual int DoSomething(WorkerThread *Thread) = 0;
};

class ModuleA : public InterfaceA
{
  // ...
  virtual int DoSomething(WorkerThread *Thread);
};

int ModuleA::DoSomething()
{
  // ...
}

class ModuleB : public InterfaceA
{
  // ...
  virtual int DoSomething(WorkerThread *Thread);
};

int ModuleB::DoSomething()
{
  // ...
}

class WorkerThread
{
  bool Attach(InterfaceA *one);
  // ...
};

bool WorkerThread::Attach(InterfaceA *one)
{
  // ...
  one->DoSomething(this);
  // ...
}

int main()
{
  // ...
  WorkerThread *workerthread = //...
  ModuleA *moda = //...
  ModuleB *modb = //...
  workerthread->Attach(moda);
  workerthread->Attach(modb);
  // ...
}

Delphi开始支持运算符重载?倒不如连模板一起支持了,
现在很多情况都是模板与运算符重载结合使用的.

无类型的思想最早是源于汇编的,而C语言是与汇编联系最紧密的高级语言.
所以无类型的思想才逐渐保留下来,这是自由的体现,也是指针发展的平台.

C++中不是所有类都有VTABLE的,
也许是OP的每个类都有VTABLE或如果使用了方法指针就自动加了VTABLE.
我认为不是简单地算一个偏移量就能解决的,
不继承基类,谁能保证类中同名方法在VTABLE中都是同样的位置?
最后编辑dwing 最后编辑于 2007-05-17 15:04:52
TOP

回复:[M] Prelude to K.O. (4)

赫赫,楼上的实现偷换概念了呢。请注意我说的是"任意类",您的实现仅仅局限在从InterfaceA继承下来的类了。

不继承基类,谁能保证类中同名方法在VTABLE中都是同样的位置?


为什么要保证?有一个好听的术语叫"Compiler Magic"...
你觉得 把一个类的一个方法的地址付给一个指针,这个操作在编译器在处理的时候和静态赋值有什么区别么?
就象我前面说的那样,顶多多计算一下偏移量罢了。
虚函数?没问题,这个虚函数是怎么调用的,这个使用这个函数指针时按同样方法处理就行了。

无类型的思想最早是源于汇编的,

这个是没办法而已吧||||

而C语言是与汇编联系最紧密的高级语言.

赫赫,这又提醒了我,使用Inline汇编,C/C++还有一个做不到的功能Pascal可以。

在一个函数中Inline两段汇编,C/C++没有办法从一段汇编的中间直接跳到另外一段里面。

Pascal Code:

Function A(A: integer): Integer;
Label Jump1;
BEGIN
// 第一段
ASM
  // 一些指令
  JMP Jump1
  // 一些指令
END;

// 一些"高级"代码

// 第二段
ASM
  // 一些指令
Jump1:
  // 一些指令
END;
END;

本来很强大的C Inline汇编在如此关键的地方变得无力,让人觉得很是意外。

(注: "巧合"的是,M$的C++语言在这里也有和Object Pascal相同的扩展...hmm让人觉得意外的亲切呢)
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

是不是任意类是无意义的.
既然写了DoSomething方法,就是说要为WorkerThread而实现,
那么就要遵守WorkerThread的规则去继承InterfaceA又有何不妥呢?
而且我认为继承InterfaceA显式声明了要为WorkerThread而创建的类才是正途.
否则编译后所有的类的所有方法名称都要在可执行文件中保留.

InterfaceA只有一个虚函数,其他要成为Module的都要继承,
这是很正常的,许多C++大型工程,多继承(尤其是继承接口)是很常见的.
C++里的VTABLE可以说是动态调用方法中效率最高的.
如果像java那样还保留函数名的信息不但有运行效率问题,还容易被反编译.

以下代码编译通过(VC6+sp6):
void main()
{
        __asm
        {
                jmp next_
        }

        int a=123;

        __asm
        {
next_:
                nop
        }
}

不过我承认VC里的_emit指令确实不如Delphi里的db/dw/dd指令好用.
C++的inline函数和C/C++的宏都是OP没法做到的.
最后编辑dwing 最后编辑于 2007-05-17 16:38:33
TOP

回复:[M] Prelude to K.O. (4)

以下代码编译通过(VC6+sp6):
void main()
{
        __asm
        {
                jmp next_
        }

        int a=123;

        __asm
        {
next_:
                nop
        }
}


这就是我说的M$的令人熟悉的扩展啊。可惜不是标准化的,没有移植性,拿给GNU编译器就会爆掉。
其实,VC从Delphi学了很多东西才能达到今天的易用程度,比如你敲一个对象,然后加"."旁边出来一个小窗口告诉你所有可用的方法和成员变量。

怎么说呢,学习并不是坏事。昨天翻Google还看到Intel的CPU技术其实很多都是从苏联学来的:
当苏联解体的时候,Intel秘密的收容了苏联国家处理器实验室的一位科学家,于是才有了Pentium I-III系列的CPU——里面用的一些技术(比如 SuperScaler Execution,超标量执行)是苏联80年代就开发出来了的。

------

另外Pascal的开域语句也是特色。

Pascal:

With StructA.StructB.StructC.StructD do
BEGIN
IntA:= 0;
IntB:= 6;
IntC:= 9;
END

C/C++:
StructA.StructB.StructC.StructD.IntA:= 0;
StructA.StructB.StructC.StructD.IntB:= 6;
StructA.StructB.StructC.StructD.IntC:= 9;

丑吧.....当然,如果用非标准化的语法,稍微好一点:
{
// 代码中混合变量声明,C/C++的特色,可惜非标准,现在大多数编译器都会警告
StructD* PtrD = &StructA.StructB.StructC.StructD;
PtrD->IntA:= 0;
PtrD->IntB:= 6;
PtrD->IntC:= 9;
}

还有,Pascal的类型指针自动解析也是很是美的。

Pascal:

// 一个RecordX的实例
StructX: RecordX;
StructX.IntC:= 123;

// 指向一个RecordX的指针
PtrD: ^RecrodX;
PtrD:= @StructX;
PtrD.IntC:= 456; // 一样用"."解析,编译器自动帮忙

C/C++:

RecordX StructX;
StructX.IntC:= 123;

RecordX* PtrD = &StructX;
PtrD->IntC:= 456;

// 一会儿用"."一会儿用"->"多麻烦啊

当要访问一个很复杂的结构中"埋藏"很深的纪录的时候,用C++要交叉写很多"."和"->",如果是在一个并列的条件语句中的话,完全可以把人写晕:
if (((StructD->StructE.StructF->StructG.StructX.IntC > 5) || (StructD->StructE.StructF->StructG.StructX.IntC < 10)) && (StructD->StructE.StructF->StructG.StructX.IntC != 7)) {}
如果用临时变量的话,基本避免不了非标准化(与代码混在一起),因为很多的时候你要先确认深层结构确实存在才能引用。

------

当然C的结构指针运算还是很不错的:
RecordX* PtrD = &StructX;
PtrD++; // 指向下一个邻接的结构

Pascal不能对任意指针作运算,只有一个"丑"一点的解决方案:
// 先定义一个很大的虚序列的指针
Type
StructXArray = Array [0..100000] of StructX;
PStructXArray = ^StructXArray;
Var
PtrD: PStructXArray = @StructX;

// 通过编号来访问下一个邻接的结构
PtrD[1].IntC:= 123;

------

学会了解并欣赏其他语言的优美,这就是我这次和您版聊的目的,我想已经达到了。:D
最后编辑Prz 最后编辑于 2007-05-18 01:23:10
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

其实__asm根本就不是C/C++标准里的,
真正的高级语言标准是不允许嵌入汇编的,这影响到源代码的可移植性.
所以讨论inline汇编是不是标准,是不是扩展毫无意义.
VC的编译器基本上是Win32平台事实上的"标准",
GNU的编译器在Win32平台还无法与之抗衡.
所以请不要说GNU不能做的事是标准,VC能做的事是不标准.
很喜欢VC的"naked call",好像其他任何高级语言编译器都不支持这一很底层的特性.
这也是有人说VC"万能"的原因.
我认为您不要再对VC有什么偏见,
我是从VC6开始用的,VC可能也是从这时开始强大起来的,
VC2003更是达到了巅峰,据说对C++标准化的支持比GNU的还好一点,
XP操作系统和XP的补丁大部分都是用VC2003开发的.
累计到目前超过一半的游戏都是用VC开发的,目前使用VC6开发游戏的仍有不少.

"比如你敲一个对象,然后加"."旁边出来一个小窗口告诉你所有可用的方法和成员变量。"
这种特性还有必要说谁学谁吗,
VC有VAX做更高级的辅助功能,输入"b"就能智能提示出"bool",输入"class"就自动给出代码框架.
我不知道Delphi是不是有这些功能.如果没有,Delphi不去学就是优点吗?

-------------------------------------------------------

多层结构体的问题,下面的写法才是正确的:
struct StructD {int id;};
struct StructC {StructD sd;};
struct StructB {StructC sc;};
struct StructA {StructB sb;};

StructA sa;
StructD* psd = &sa.sb.sc.sd;
psd->id=1;
即使打开VC的最高警告级别也没有任何警告提示.
我想知道为什么说这是不标准的.

这与下面的写法看起来没有任何不美观的地方:
With StructA.StructB.StructC.StructD do
BEGIN
IntA:= 0;
END

其实OP实现那段代码,也要取StructD的指针.

-------------------------------------------------------

"一会儿用"."一会儿用"->"多麻烦啊"

晕了,微软提倡的匈牙利命名方法比这更麻烦,怎么还有人大力赞扬?
还有OP的"begin/end"比"{}",":="比"="等等更是麻烦...
其实很难能找到一个比C/C++更简洁的高级语言了.
还有一点值得一提,如果有VAX辅助,应该输入"->"却输入"."时会自动修正成"->".
VAX是我见过代码输入辅助软件中辅助能力最高,智能程度最高的,敲代码如行云流水一般.
比eclipse还强一点.

-------------------------------------------------------

我写过不知多少行代码,很难见到"深层结构",
真是如此,就应该看看结构设计的是否合理了.
最后编辑dwing 最后编辑于 2007-05-18 09:19:57
TOP

回复:[M] Prelude to K.O. (4)

其实__asm根本就不是C/C++标准里的,
真正的高级语言标准是不允许嵌入汇编的,这影响到源代码的可移植性.
所以讨论inline汇编是不是标准,是不是扩展毫无意义.


那么,你的意思也就是说,你前面说Object Pascal没有"标准化"也是毫无意义的喽?


即使打开VC的最高警告级别也没有任何警告提示.
我想知道为什么说这是不标准的.

这与下面的写法看起来没有任何不美观的地方


赫赫,果然又在吧M$当成标准了。说老实话,M$的特点就是"不标准",而且非常喜欢把本来标准的东西拿过来乱改。
ISO规范化的C是不提倡将代码和变量声明混合在一起的,因为这样代码可维护性差。你试试用gcc编译就知道了。

前面的帖子我就说过,VC的跨asm段跳转是一个严重不兼容的语言扩展,放在其他编译器下就会爆掉,连这个VC都不会警告,你还期望它警告一个不提倡的使用方法?

不美观是因为多用了一个变量,多写了一行。
如果编译器够聪明,会发现这个变量仅仅是为了展开结构,也就会自动省掉。
但是,这样其实也就相当于把程序运行的效率人为的降低,然后把提升的希望放在编译器上。

VC有VAX做更高级的辅助功能,输入"b"就能智能提示出"bool",输入"class"就自动给出代码框架.
我不知道Delphi是不是有这些功能.如果没有,Delphi不去学就是优点吗?


Delphi上用滥的功能,而且我充分相信也是Delphi首先引入的功能。

或许你应该学学历史,Turbo Pascal从出现到离开主流都是世界首屈一指的IDE开发环境。
C程序员还需要在单独的文本编辑器写程序,然后花数分钟时间编译,最后再链接的时候,TP只需要一个键就可以了。
光标移到一个函数上,一键就可以立刻获得这个函数的接口和相关帮助信息,这个是80年代的事情。那个时候写C的程序员还在来回的翻书吧。
不要忘了,Borland就是靠优秀的开发环境,不断的为快速程序开发创新,才能生存于M$的美元大棒下的。

还有OP的"begin/end"比"{}",":="比"="等等更是麻烦...
其实很难能找到一个比C/C++更简洁的高级语言了.


begin end和{}的区别,大概就是敲键盘上三个键的位置不同罢了。({+} 确实比 b+<回车> 隔的要近一点,你硬要说这个是天大的优势,我也没办法)

至于:=和=嘛,赫赫,请你自己回忆一下,不小心把 == 写成 = 曾经浪费了你多少的时间?


我写过不知多少行代码,很难见到"深层结构",


任何需要处理大量关系数据的场合: 人工智能图像识别, 搜索引擎用的网络爬虫...
我算是对"->"深恶痛绝了..... =v=
最后编辑Prz 最后编辑于 2007-05-18 10:59:33
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

……寒,居然变成战帖了

粗略的看了一下,感觉大致是COM的简化版加上一个异步消息处理机制。MISHA你也开始玩架构了哦……

个人在ARM及X86上都很喜欢用ASM写万行+代码PROJECT并且全面优化(通常是最小SIZE和最大速度混合)。不为别的,只是好玩而且看起来很COOL而已(不过IA64是不可能的orz)。比起类我还是更喜欢用指针,实用至上主义,搞小东西用BASIC,搞严谨点的用C。OO语言太费劲了,光是设计结构都要想半年,不是老板要求的话一定不用,呵呵
最后编辑LOVEHINA-AVC 最后编辑于 2007-05-18 11:11:19
TOP

回复:[M] Prelude to K.O. (4)

赫赫,是啊,我觉得没有必要"口水战"下去了,原因很简单,这个世界上跟本就没有什么东西是完美,语言也不例外。
从ASM到Perl随便举一种语言我都可以找出一堆比C++好的地方,反之亦然。

最后重申一下我从开始到现在一直的立场:
学会了解并欣赏其他语言的优美


本讨论可以结束了 =v=
最后编辑Prz 最后编辑于 2007-05-18 11:22:34
飛べない翼に、意味はあるんでしょうか?
TOP

回复:[M] Prelude to K.O. (4)

14楼的程序略作改动就可以在1990年的Microsoft Quick C 2.51上编译通过,说明这个扩展并不是从Delphi学来的。
void main()
{
    int a=123;
        asm
        {
                jmp next_
        }

        a++

        asm
        {
next_:
                nop
        }
}
此外Intel,DigtalMars的C++编译器也支持这种用法,但Borland C++不支持,而GCC的汇编语法就和Intel系的汇编语法都不一样。

C++的typedef可以提供类似Object Pascal开域语句的功能,用它来简化复杂类型是常见的手法:
    struct StructA
    {
        struct StructB
        {
            struct StructC
            {
                struct StructD
                {
                    int id;
                }sd;
            }sc;
        }sb;
    };

    StructA sa;
    typedef StructA::StructB::StructC::StructD StructD;
    StructD& psd = sa.sb.sc.sd;
    psd.id=1;

另外现在C++的编程思想是,能用reference的地方不用pointer;能用template代替virtual function的时候不用virtual function,至于模版怎么用,建议参考Loki和Boost.Spirit这两个library。
虽然C++代码在Size方面不是很好,但Speed不会比C和汇编差。
KEYFC第二届版杀 - 川澄 舞
TOP