C++与Object Pascal相比之不爽处

SeaWave 2004-12-20 10:19:34
首先声明,我无意比较两种语言之间的优劣,只是在实际使用过程中表达一点自己的看法,同时,更重要的是我对C++的语法不如Pascal熟,想借此向各位C++高手印证一下,是否下面的“不爽”是由于我自己对C++语法不熟而造成的,如果是,请各位指教正确的用法,不胜感激,谢谢。

第一个问题:关于抽象
先看C++的代码:
#include <iostream.h>

class CTest {
public:
virtual void Teardown(void) = 0; //抽象方法声明
void Foo(void);
virtual ~CTest() { Teardown(); }; //企图规定所有实现CTest的
//对象类在析构时必须调用Teardown()
};

void CTest::Foo(void)
{
cout << "CTest::Foo() called\n";
}

class CTestImp : public CTest {
public:
virtual void Teardown(void); //实现Teardown()
};

void CTestImp::Teardown(void)
{
cout << "CTestImp::Teardown()\n";
}

void cdecl main(void)
{
CTest * test = new CTestImp; //建立对象
test->Foo(); //调用对象的方法,成功
delete test; //销毁对象,运行时刻错,提示
//Pure virtual function called
}
这一点在PASCAL里是不存在的,在PASCAL里的析构函数中,可以方便地调用抽象方法,从而强制规定一个对象在析构的时候,必须执行某种操作。参考DELPHI中的代码:
type
TTest = class
public
destructor Destroy; override; //析构方法
procedure Teardown; virtual; abstract; //抽象方法
procedure Foo;
end;

TTestImp = class(TTest)
public
procedure Teardown; override; //实现父类的抽象方法
end;

procedure TTestImp.Teardown;
begin
ShowMessage('TTestImp.Teardown called');
end;

destructor TTest.Destroy;
begin
Teardown; //规定所有子类在析构的时候都要调用Teardown方法
inherited;
end;

procedure TTest.Foo;
begin
ShowMessage('TTest.Foo called');
end;

// 下面是测试代码,运行成功,达到目的
procedure TForm1.FormCreate(Sender: TObject);
var
test: TTest;
begin
test := TTestImp.Create;
try
test.Foo;
finally
test.Free;
end;
end;

第二个问题:关于try ... finally块
DELPHI中的try ... finally非常出色,而C++中就有遗憾。首先,标准C++里只有try和catch,而没有finally,非常不方便,当我们想无论如何都要执行某段代码时,用标准C++实现起来很麻烦,比方说我们想在申请内存后执行一些操作,而无论这些操作是否出错,都要执行释放内存的代码。或者创建一个对象,执行一些操作,最后无论如何都销毁这个对象。PASCAL中用try...finally非常方便,参见上面的test.Free那段代码。

而在Win32平台下的C++,由于Windows本身提供了SEH(结构化异常处理)机制,所以VC++中定义了__try、__catch和__finally,但是首先它们不是标准C++,其次它们只依赖于特定平台,最后,下面的代码在编译时刻无法通过:
class CTest {
public:
virtual void Foo(void) = 0;
};

class CTestImp : public CTest {
public:
virtual void Foo(void);
};

void CTestImp::Foo(void)
{
MessageBox(0, "00000", NULL, MB_OK);
}

void test(void)
{
CTest * test = new CTestImp();
__try {
test->Foo();
} __finally {
delete test;
}
}
编译错误信息:
error C2712: Cannot use __try in functions that require object unwinding

------------上面两点是最近我用C++重写以前程序时遇到的问题,请大家指正。
...全文
296 12 打赏 收藏 转发到动态 举报
写回复
用AI写文章
12 条回复
切换为时间正序
请发表友善的回复…
发表回复
xzgyb 2004-12-21
  • 打赏
  • 举报
回复

谢谢ilovevc(ilovevc)的解释
受益了
ilovevc 2004-12-21
  • 打赏
  • 举报
回复

第二点,try,finally我认为恰恰是C++的强项,而不是弱项。
C++有一个很重要的特性就是在栈中构造的对象在stack unwinding/退出作用域的时侯析构函数被自动调用。Delphi从TObject派生的对象必须使用new的方式创建,而不能直接建立在栈上。因此丧失了这种特性。

可以利用这种特性将一个资源的释放放入一个建立在栈上的对象中。C++有个专门的术语叫RAII,资源获取即初始化。其实隐含的意思是“资源释放当析构”。

就你上面的那个例子,可以定义一个模板类(C++标准库中已经有了一个auto_ptr,下面是一个简化的版本)

template<class T> struct auto_delete
{
auto_delete(T * p) : m_p(p) {}
~auto_delete() { delete m_p;}
T* operator->() { return m_p; }
T * m_p;
}

然后你就可以如下使用:

void test()
{
auto_delete<TTest> test(new TTestImp);
test->Foo();
}

你和try,finally比较比较哪种代码更加简单?

使用模板是因为可以避免重复的代码,对TTest可以用,对任何只要通过 delete p就可以释放资源的class都可以用,一劳永逸。而且该模板中都是inline函数,编译器稍微聪明点的话,根本无需实例化真正的模板类,完全等同于你手工打造的代码。


然后你可能又有疑问,那么对于其他不能通过delete释放的资源又如何呢?例如文件什么的。这种情况下,当然我们又需要写一个类似auto_delete的class用于释放文件资源。当然有点麻烦。

但是记住的是,只要你建立好了这个class,那么所有使用文件的地方你都可以无需手动释放资源了。
而使用try,finally,每个你用到文件的函数你都得try,finally一次。一般来说一种资源很少就使用一次,因此对于简化代码是很有帮助的。


上面所说的可以在C++创始人的主页上找到。祝你早日以C++的风格行事。
SeaWave 2004-12-21
  • 打赏
  • 举报
回复
现在才发觉好象分给少了,不太好分给大家 :(
结贴了。谢谢大家
SeaWave 2004-12-20
  • 打赏
  • 举报
回复
呵呵,在回完一楼时,才看到二楼的回复(影子传说),所以三楼的回复是针对一楼的,不好意思。

二楼(影子传说)的回答让我豁然醒悟----派生类先析构,然后才是基类的析构。非常感谢,但是我的问题又来了,在PASCAL里又是如何实现的呢?
在PASCAL里也是先调用派生类的析构,然后才调用基类的析构,但是基类的析构方法却可以调用抽象方法,很奇怪,看来DELPHI在某个地方保存了派生类的信息。(也许吧)

再次感谢影子传说,如果再帮忙解释一下C++里的try ... finally就更感谢了 :P
sharkhuang 2004-12-20
  • 打赏
  • 举报
回复
__try
__except ( expression )
SeaWave 2004-12-20
  • 打赏
  • 举报
回复
谢谢楼上的回复,不过有点问题:
  看main()里面的代码CChild * pChild=new CChild()这句,这句是符合语法的,但是不符合编程思想,试想,如果我已经知道具体的实现类了,还有什么必要来定义抽象类呢?通常符合编程思想的代码应该是:
CGrandFather * pObj = new CChild;
这样才是抽象的本意,当然,这里把CChild硬编码在代码行里明显的不好的,更通常的作法可能是工厂模式,比方说:CGrandFather * pObj = CGrandFatherFactory::Create();
  扯远了,回过头来看楼上的例子,如果编译器已知pChild是一个指向CChild的指针,而不是一个指向CGrandFather的指针,那它代成的代码毫无问题,因为编译器知道(无论是编译时刻还是运行时刻)这个对象是属于CChild这个类的,而CChild里不是抽象类,所以生成的代码无论怎么运行都不会有抽象错。但是这样就失去抽象的本意了。
  而事实上我们要的是,编译器在编译时刻并不知道某个在运行时刻动态建立的对象是哪个具体的实现类,它只知道这个对象的抽象(CGrandFather * pObj),编译器可以能过这种代码,也是必须通过的。通过虚方法表,代码在运行时刻来决定这个抽象类的具体实现,也可以访问它的抽象方法(因为子类已经实现了)。
pacman2000 2004-12-20
  • 打赏
  • 举报
回复
在基类的析构函数里,是不能调用派生类的虚函数的。因为派生类先析构,所以在执行基类析构时,派生类已经析构完成了,这时候调用派生类的虚函数,是极其危险的。所以,这时的函数,只能是基类的函数(所以报错是说调用了纯虚函数)。也就是虚拟机制在基类析构函数中不起作用!
wangbab 2004-12-20
  • 打赏
  • 举报
回复
抽象的方法只是一个类型。什么都不做。具体实现还是要子类来完成的。
定义了纯虚函数的类为纯虚类。什么都不做,只是用于pTable的访问。
父类的析构里执行抽象方法应该是不行的。除非
class CGrandFather
{
virtual void Teardown(void) = 0; //抽象方法声明
CGrandFather();
virture ~CGrandFathrer();
}
class CFather : public CGrandFather
{
virtural void Teardown(void) { ....};
CFather();
virture ~CFather() { this->Teardown();};
}
class CChild : public CFather
{
CFather();
virture ~CChild();
}
main()
{
CChild *pChild=new CChild();
delete CChild();
}
yjh1982 2004-12-20
  • 打赏
  • 举报
回复
Pascal向来已幽雅著称,
但Object Pascal不是标准语言,和windows绑得太紧
xzgyb 2004-12-20
  • 打赏
  • 举报
回复
刚做了个试验
#include <iostream.h>

class Base
{
public:
virtual ~Base()
{
func();
}

protected:
virtual void func()
{
cout << "Base::func" << endl;
}
};

class Derive: public Base
{
protected:
virtual void func()
{
cout << "Derive::func" << endl;
}
};

void main(void)
{
Base* b = new Derive;

delete b; //这个打印出来的是Base::func
}
在vc6中跟踪调试了一下
对于进到Base的虚构函数时,编译器会把当前的虚表指针指向Base的虚表,而不是Derive的虚表
004011F0 C7 00 54 60 42 00 mov dword ptr [eax],offset Base::`vftable' (00426054)

所以呢,在基类的析构函数里调用抽象方法是肯定出错的

而delphi则不存在这样的替换


goodluckyxl 2004-12-20
  • 打赏
  • 举报
回复
深入了解了两者的差别再下结论吧
析构的顺序性保证资源的释放
构造的顺序性保证当前构造建立在已经构造的对象资源之上
影子已经说了就不多说了
----------------------------------------------------

PS: pascal语言在borland大放光芒,是一门优秀的语言
喜欢borland
beyondtkl 2004-12-20
  • 打赏
  • 举报
回复
在PASCAL里也是先调用派生类的析构,然后才调用基类的析构,但是基类的析构方法却可以调用抽象方法,很奇怪,看来DELPHI在某个地方保存了派生类的信息。(也许吧)

// 最好不要這樣 這是不安全的。。

try

catch(someexcpetion& e)

finally

64,685

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧