纯虚拟析构函数 [英] Pure virtual destructor

查看:94
本文介绍了纯虚拟析构函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您好,

我正在浏览Marshal Cline的C ++ FAQ-Lite。


我对33.10节有疑问。 />

这里他在基类中声明了一个纯虚拟析构函数。


再次将其定义为内联。像这样。


inline Funct ::〜Funct(){} //定义即使它是纯虚拟的;以这种方式更快的是
;相信我


我怀疑是:


1 ..为什么我们应该让析构函数变得虚拟。有什么好处?

2 ..如果它是纯虚拟的,那么

基类中不应该有任何定义。它应该只在派生类中定义,这样我们就可以创建派生类的实例。

3 ..但是在这种情况下它不会在派生类中重新定义。什么是

原因?

4 ..再次如何禁止它在Base类中定义内联?因为编译器不会遵守

内联请求,如果它是虚拟的。

我附上FAQ的33.10部分供您参考。

[33.10]什么是函数,我为什么要使用?


函数类似于类固醇的功能。功能强大比函数更强大,并且额外的功能解决了使用函数指针时通常遇到的一些(不是全部)

挑战。


让我们看一个显示传统使用函数指针的例子,然后我们将把这个例子翻译成functionoids。传统的

函数指针的想法是拥有一堆兼容的函数:


int funct1(... params ...){...代码...}

int funct2(... params ...){... code ...}

int funct3(... params .. 。){... code ...}


然后你通过函数指针访问它们:


typedef int(* FunctPtr) (... params ...);


void myCode(FunctPtr f)

{

...

f(... args-go-here ...);

...

}


有时人们会创建这些函数指针的数组:


FunctPtr数组[10];

array [0] = funct1;

array [1] = funct1;

array [2] = funct3;

array [3] = funct2;

。 ..


在这种情况下,他们通过访问数组来调用函数:


array [i](... args-go-这里...);


使用functionoids,你首先用纯虚方法创建一个基类:


class功能{

public:

virtual int doit(int x)= 0;

virtual~Funct()= 0;

};


inline Funct ::〜Funct(){} //定义即使它是纯虚拟的;以这种方式更快的是
;相信我


然后你创建三个派生类而不是三个函数:


class Funct1:public Funct {

public:

virtual int doit(int x){...来自funct1的代码......}

};


class Funct2:public Funct {

public:

virtual int doit(int x){...来自funct2的代码...}

};


class Funct3:public Funct {

public:

virtual int doit(int x){..来自funct3的代码...}

};


然后传递函数指针而不是传递函数指针。我将创建一个名为FunctPtr的

typedef,仅仅是为了使其余的代码类似于

的老式方法:


typedef Funct * FunctPtr;


void myCode(FunctPtr f)

{

...

f-> doit(... args-go-here ...);

...

}

你可以用几乎相同的方式创建它们的数组:


FunctPtr数组[10];

array [0] = new Funct1(... ctor-args ...);

array [1] = new Funct1(... ctor-args ...);

array [ 2] =新的Funct3(... ctor-args ...);

array [3] = new Funct2(... ctor-args ...);

...


这给了我们第一个关于函数体强度比函数指针更强大的地方的暗示:函数式方法有这样的事实

参数你可以传递给ctors(上面显示为... ctor-args ...)而

函数指针版本没有。将一个functionoid对象想象为一个冻结函数调用(强调单词调用)。与函数指向

的指针不同,functionoid是(概念上)指向部分调用的

函数的指针。想象一下目前一项技术可以让你通过一个但不是所有的参数传递给函数,然后让你冻结那个

(部分完成)的调用。假设技术为你提供了某种类型的魔法指针,用于冻结部分完成的函数调用。

然后你使用该指针传递剩余的args,和系统

神奇地拿走你的原始args(被冻干),将它们与
结合起来,并且函数计算之前的任何局部变量都是

冻干,将所有这些与新传递的args相结合,并继续执行函数的执行,冻结时它停止运行。那个

可能听起来像是科幻小说,但它在概念上是什么功能块

让你这么做。而且他们让你反复完成用各种不同的剩余参数冷冻干燥

函数调用,就像你经常一样b $ b喜欢。另外,当它被调用时,它们允许(不要求)你改变冻干状态

,这意味着functionoids可以记住从一个

调用到下一个的信息。 />

好​​的,让我们重新站起来,我们将用几个

的例子来解释所有那些mumbo jumbo真正意味着什么。


假设原始函数(采用老式函数指针样式)

参数略有不同。


int funct1(int x,float y)

{... code ...}

int funct2(int x,const std: :string& y,int z)

{... code ...}

int funct3(int x,const std :: vector< double> ;& y)

{...代码...}


当参数不同时,老式的函数指针

方法难以使用,因为调用者不知道要传递哪些参数

(调用者只有一个指向功能,而不是函数'

名称,或者,当参数不同时,它的

参数的数量和类型)(不要给我写一封关于这个;是的,你可以做到,但是你必须站在你的头上并做一些凌乱的事情;但是不要写我关于

它 - 改为使用functionoids。


使用functionoids,情况至少有时会好得多。由于

a functionoid可以被认为是一个冻干函数调用,只需要使用

这些非常见的args,比如我称之为y和/或z,并使它们成为相应的ctors的
args。你也可以将常见的args(在这个

的情况下,int调用x)传递给ctor,但你不必 - 你有

选项将它/它们传递给纯虚拟doit()方法。我会

假设你想传递x并进入doit()和y和/或z进入ctors:


class Funct {

public:

virtual int doit(int x)= 0;

};


然后代替在三个函数中,你创建了三个派生类:


class Funct1:public Funct {

public:

Funct1(float y ):y_(y){}

virtual int doit(int x){...来自funct1的代码......}

private:

float y_;

};


class Funct2:public Funct {

public:

Funct2(const std :: string& y,int z):y_(y),z_(z){}

virtual int doit(int x){...来自funct2的代码.. 。}

私人:

std :: string y_;

int z_;

};


class Funct3:public Funct {

public:

Funct3(const std :: vector< double>& y):y_( y){}

virtual int doit(int x){...来自funct3的代码......}

priv吃了:

std :: vector< double> y_;

};


现在你看到ctor'的参数冻结成了函数

你创建了functionoids数组:


FunctPtr数组[10];


数组[0] =新的Funct1(3.14f);


array [1] = new Funct1(2.18f);


std :: vector< double> bottlesOfBeerOnTheWall;

bottlesOfBeerOnTheWall.push_back(100);

bottlesOfBeerOnTheWall.push_back(99);

...

bottlesOfBeerOnTheWall.push_back(1);

array [2] = new Funct3(bottlesOfBeerOnTheWall);


array [3] = new Funct2(" my string",42);


...


因此,当用户在其中一个函数体上调用doit()时,他

提供剩余资金。 args,并且调用概念上将传递给ctor的原始args与传递给doit()方法的args结合起来:


array [i] - > ; doit(12);


正如我已经暗示的那样,函数类型的一个好处是你可以

有多个实例,比如说,你的数组中的Funct1,以及那些实例

可以将不同的参数冻结成它们。例如,array [0]

和array [1]都是Funct1类型,但是array [0] - > doit(12)

的行为将与array [1] - > doit(12)的行为不同,因为行为

将取决于传递给doit()的12和传递给
的args
ctors。


如果我们将

函数数组中的示例更改为本地函数,那么函数体的另一个好处是显而易见的。为了设置舞台,让我们回到

回到老式的函数指针方法,并想象你试图通过比较 - >
函数到sort()或binarySearch()例程。

sort()或binarySearch()例程被称为childRoutine()和

比较函数指针类型是叫做FunctPtr:


void childRoutine(FunctPtr f)

{

...

f( ... args ...);

...

}


然后不同的调用者会传递不同的函数指针取决于他们认为最好的



void myCaller()

{

。 ..

childRoutine(funct1);

...

}


void yourCaller( )

{

...

childRoutine(funct3);

...

}


我们可以使用functionoids轻松地将这个例子翻译成一个:


void childRoutine(Funct& f)

{

...

f.doit(... args ...);

。 ..

}


无效myCaller()

{

...

Funct1功能(... ctor-args ...);

childRoutine(功能);

...

}


void yourCaller()

{

...

Funct3功能(。 ..ctor-args ...);

childRoutine(功能);

...

}

以此示例为背景,我们可以看到函数指针的两个好处

。 ctor args上面描述的好处,加上

事实,functionoids可以保持线程安全

方式之间的状态。使用普通的函数指针,人们通常通过静态数据维持

调用之间的状态。然而,静态数据本质上不是线程安全的 - 静态数据在所有线程之间共享。 functionoid

方法为你提供了本质上是线程安全的东西,因为代码最终得到了线程本地数据。实现是微不足道的:

将老式静态数据更改为

functionoid'这个对象内的实例数据成员,而poof,数据不仅仅是线程本地,但是

它对于递归调用甚至是安全的:每次调用yourCaller()都会有自己独特的Funct3对象和它自己独特的实例数据。 br />

请注意,我们已经获得了一些东西而没有丢失任何东西。如果你想要

线程全局数据,functionoids也可以给你:只需将它从

更改为functionoid'这个对象内的实例数据成员为静态

在functionoid'类中的数据成员,甚至是本地范围的静态
数据。你不会比使用功能指针更好,但你也不会更糟糕。


功能方法为你提供第三个选项不可用

采用老式方法:functionoid允许调用者决定是否需要线程局部或线程全局数据。他们有责任在他们想要线程全局数据的情况下使用

锁,但至少他们会有b $ b可以选择。这很简单:


void callerWithThreadLocalData()

{

...

Funct1功能(... ctor-args ...);

childRoutine(功能);

...

}


void callerWithThreadGlobalData()

{

...

静态Funct1功能(... ctor- ARGS ...); ?静态是唯一的

差异

childRoutine(功能);

...

}


在制作灵活的

软件时,函数不能解决所遇到的每一个问题,但是它们比功能指针更强大,并且

他们至少值得评估。实际上你可以很容易地证明

的函数不会失去对函数指针的任何影响,因为你可以想象一下函数指针的老式方法相当于拥有全局(!)functionoid对象的
。既然你总能制作一个全球性的b $ b函数对象,你就没有失去任何理由。 QED。


[顶部|底部|上一节|下一节|搜索常见问题]


解决方案

* santosh:


我是通过Marshal Cline的C ++ FAQ-Lite。

我对第33.10节有疑问。


这就是那个标题为什么是一个类似的东西,为什么我会使用

一个?。


如果你已经包含了这个标题就好了,所以我们可以知道

你在谈论什么,嗯,好吧! :-)


我对术语functionoid有疑问;这些小物件是通常被称为仿函数的
。在C ++中,以及其他语言中的其他东西,以及

我还没有遇到过functionoid这个术语。除了常见问题之外的任何地方。


在这里,他在基类中声明了一个纯虚拟析构函数。

再次将其定义为内联。像这样。

内联函数::〜Funct(){} //定义即使它是纯虚拟的;它的速度更快;相信我


似乎没有理由在该类中声明析构函数为

pure virtual;我不知道为什么会这样(编辑错误?)。


我怀疑是:

1 ..为什么我们应该让析构函数变成虚拟的。有什么好处?


如果您手边没有其他功能,那么析构函数就是一个方便的版本

来制作纯虚拟版。


这使得课程在语言层面上有效抽象。


通常用于所谓的标记界面。


2 ..如果是纯虚拟的,那么
基类中不应该有任何定义。它应该只在派生类中定义,这样我们就可以创建派生类的实例。


不,这是不正确的。


由于析构函数被调用,因此_must_是它的定义。

3 ..但在这种情况下,它不会在派生类中重新定义。原因是什么?


如果没有声明,编译器会自动生成析构函数。


4 ..再次如何禁止它在Base中定义内联类?因为编译器不会遵守内联请求,如果它是虚拟的。


我认为关于更快的评论(以及析构函数是纯粹的
的东西。


我附上FAQ的33.10部分供你参考。




不要这样做!


有些人仍然通过缓慢的模拟线路阅读新闻。


请务必体谅;如有必要,请提供一个URL。


-

答:因为它弄乱了人们通常阅读文本的顺序。

问:为什么这么糟糕?

A:热门发布。

问:usenet和电子邮件中最烦人的是什么?


* santosh:

因为编译器不会遵守内联请求,如果它是虚拟的。




对不起,这是不正确的。


不是''inline''是编译器必须遵守的请求

无论如何。


但是编译器可以自由调用虚函数的内联函数,并且如果它是一个好的编译器,它将是b $ b,它会,在适当的时候。


-

答:因为它弄乱了人们通常阅读文本的顺序。

问:为什么这么糟糕?

A:热门发布。

问:usenet和电子邮件中最烦人的是什么?

>我的疑问是:


1 ..为什么我们应该让析构函数纯粹虚拟。有什么好处?


纯虚函数用于使类抽象化。当一个类可能没有任何其他候选人可以成为纯虚拟的
成员函数时,可以使析构函数为纯虚拟的
。在这种情况下,相反具有虚拟纯虚函数的
,你可以使析构函数

纯虚拟。

2 ..如果它是纯虚拟的,在基类中不应该有任何定义。它应该只在派生类中定义,这样我们就可以创建派生类的实例。


纯虚拟析构函数_have_有一个定义。这是因为,在继承heirarchy的同时,在销毁派生类对象时,

调用类层次结构中的所有析构函数。如果你可以将
省略纯虚析构函数的定义,那么

就没有析构函数体可以调用。

3。但在这种情况下,它不会在派生类中重新定义。原因是什么?


不需要在派生类中定义纯虚拟析构函数。

的原因是这个。基类中的普通纯虚函数将导致派生类是抽象的,除非它是在

派生类中定义的。但是,如果你自己不给它,那么析构函数会由

编译器自动提供。

4 ..再次如何禁止它在Base类中定义内联?因为编译器不会遵守内联请求,如果它是虚拟的。


我不确定它会如何更快。


问候,

Srini

santosh写道:您好,
我正在审阅Marshal Cline的C ++ FAQ-Lite。

我对第33.10节有疑问。

在这里,他在基类中声明了一个纯虚拟析构函数。

并再次将其定义为内联。像这样。

内联函数::〜Funct(){} //定义即使它是纯虚拟的;它的速度更快;相信我

我的疑问是:

1 ..为什么我们应该让析构函数变得虚拟。有什么好处?
2 ..如果它是纯虚拟的,那么在基类中不应该有任何定义。它应该只在派生类中定义,以便我们可以创建派生类的实例。
3 ..但在这种情况下,它不会在派生类中重新定义。是什么原因?
4 ..再次如何禁止它在Base类中定义内联?因为编译器不会遵守内联请求,如果它是虚拟的。

我附上FAQ的33.10部分供你参考。


[33.10]什么是功能,我为什么要使用?

功能性是类固醇的功能。功能块比功能更强大,并且额外的功能解决了使用功能指针时通常遇到的一些(并非所有)挑战。

让's'我们将一个示例展示了函数指针的传统用法,然后我们将该示例转换为functionoids。传统的
函数指针的想法是拥有一堆兼容的函数:

int funct1(... params ...){... code ...}
int funct2(... params ...){... code ...}
int funct3(... params ...){... code ...}
<然后你通过函数指针访问它们:

typedef int(* FunctPtr)(... params ...);

void myCode(FunctPtr f)
{
...
f(... args-go-here ...);
...
}
有时人们会创建这些函数指针的数组:

FunctPtr数组[10];
数组[0] = funct1;
数组[1] = funct1;
array [2] = funct3;
array [3] = funct2;
...

在这种情况下,他们通过访问数组来调用函数:

array [i](... args-go-here ...);

使用functionoids,首先使用纯虚方法创建一个基类:

类Funct {
public int doit(int x)= 0;
virtual~Funct()= 0;
} ;

内联函数::〜Funct(){} //定义即使它是纯虚拟的;它的速度更快;相信我

然后你创建三个派生类而不是三个函数:

类Funct1:public Funct {
public:
virtual int doit( int x){...来自funct1的代码......}
};

类Funct2:public Funct {
public:
virtual int doit(int x ){...来自funct2的代码......}
};

类Funct3:public Funct {
public:
virtual int doit(int x){ ...来自funct3的代码...}
};

然后传递函数*而不是传递函数指针。我将创建一个名为FunctPtr的
typedef,仅仅是为了使其余的代码类似于
老式的方法:

typedef Funct * FunctPtr;

void myCode(FunctPtr f)
{
...
f-> doit(... args-go-here ...);
。 ..
}

你可以用几乎相同的方式创建它们的数组:

FunctPtr数组[10];
数组[0] = new Funct1(... ctor-args ...);
array [1] = new Funct1(... ctor-args ...);
array [2] = new Funct3( ... ctor-args ...);
数组[3] =新的Funct2(... ctor-args ...);
...

这个给出了关于函数体强度比函数指针更强大的第一个暗示:函数式方法具有可以传递给ctors的参数这一事实(如上所​​示为... ctor-args。 ..)而
函数指针版本没有。将functionoid对象视为冻干函数调用(强调单词调用)。与指向函数的指针不同,函数类(概念上)是指向部分调用的函数的指针。想象一下,现在有一种技术可以让你把一些但不是所有的参数传递给一个函数,然后让你冻结那个
(部分完成的)调用。假装技术为你提供了某种魔法指针,指向冻结的部分完成的函数调用。
然后你使用该指针传递剩余的args,并且系统神奇地采取原始的args(冻干),将它们与任何局部变量结合起来,这些变量在冻干之前计算出来,将所有这些变量与新传递的args相结合,并继续
好的,让我们重新站起来,我们将用几个例子来解释所有那些真正意义上的笨蛋。

假设原始功能(在老式的函数指针风格)
采用略有不同的参数。

int funct1(int x,float y)
{... code ...}

int funct2(int x,const std :: string& y,int z)
{... code ...}
int funct3(int x,const std :: vector< double>& y)
{... code ...}

当参数不同时,老式的函数指针接近难以使用,因为调用者不知道要传递哪些参数(调用者只有指向函数的指针,而不是函数的名称,或者当参数为ar时不同的,它的数量和类型
参数)(不要给我写一封关于此的电子邮件;是的,你可以做到,但你必须站在你的头上做乱的事情;但是不要写我关于
它 - 改为使用functionoids。

对于functionoids,情况至少有时会好得多。因为一个functionoid可以被认为是一个冻干函数调用,所以只需要使用非常见的args,例如我称之为y和/或z的args,并使它们成为<相应的ctors args。你也可以将常见的args(在这个情况下称为x的int)传递给ctor,但你不必 - 你有
选项将它/它们传递给纯虚拟改为使用doit()方法。我会
假设你要传递x并进入doit()和y和/或z进入ctors:

类Funct {
公共:
virtual int doit(int x)= 0;
};

然后创建三个派生类而不是三个函数:

类Funct1:public Funct {
public:
Funct1(float y):y_(y){}
virtual int doit(int x){...来自funct1的代码......}
private:
浮动y_;
};

类Funct2:public Funct {
public:
Funct2(const std :: string& y,int z) :y_(y),z_(z){}
virtual int doit(int x){...来自funct2的代码......}
private:
std :: string y_;
int z_;
};

类Funct3:public Funct {
公共:
Funct3(const std :: vector< double>& y ):y_(y){}
virtual int doit(int x){...来自funct3的代码...}
private:
std :: vector< double> y_;
};

现在你看到当你创建功能块数组时,ctor'的参数被冻结成了functionoid


FunctPtr数组[10];

数组[0] =新的Funct1(3.14f);

数组[1] =新的Funct1(2.18f);

std :: vector< double> bottlesOfBeerOnTheWall;
bottlesOfBeerOnTheWall.push_back(100);
bottlesOfBeerOnTheWall.push_back(99);
...
bottlesOfBeerOnTheWall.push_back(1);
数组[2] = new Funct3(bottlesOfBeerOnTheWall);

array [3] = new Funct2(" my string",42);

...
因此,当用户在其中一个功能块上调用doit()时,他将提供剩余功能。 args,并且调用概念上将传递给ctor的原始args与传递给doit()方法的args相结合:

array [i] - > doit(12);

正如我已经暗示的那样,函数类型的一个好处是你可以在你的数组中有几个例如Funct1的实例,而这些实例可能会有不同的参数冻干成它们。例如,array [0]
和array [1]都是Funct1类型,但是array [0] - > doit(12)
的行为将与数组的行为不同[ 1] - > doit(12)因为行为
将取决于传递给doit()的12和传递给ctors的args。

另一个如果我们将示例从函数体的数组更改为局部函数,则函数的好处是显而易见的。要设置阶段,让我们回到老式的函数指针方法,并想象你试图将比较函数传递给sort()或binarySearch ()例程。
sort()或binarySearch()例程称为childRoutine(),而
比较函数指针类型称为FunctPtr:

void childRoutine(FunctPtr f )
{
...
f(... args ...);
...
}

然后不同调用者会根据他们认为最好的函数来传递不同的函数指针:

void myCaller()
{
...
childRoutine(funct1) );
...

取消yourCaller()
{
...
childRoutine(funct3);
...

我们可以使用functionoids轻松地将此示例翻译成一个:

void childRoutine(Funct& f)
{
...
f.doit(... args ...);
...


void myCalle r()
{
...
Funct1功能(... ctor-args ...);
childRoutine(功能);
...
}

void yourCaller()
{
...
Funct3功能(... ctor-args ......);
childRoutine(funct);
...
}

Given this example as a backdrop, we can see two benefits of functionoids
over function-pointers. The "ctor args" benefit described above, plus the
fact that functionoids can maintain state between calls in a thread-safe
manner. With plain function-pointers, people normally maintain state between
calls via static data. However static data is not intrinsically
thread-safe - static data is shared between all threads. The functionoid
approach provides you with something that is intrinsically thread-safe since
the code ends up with thread-local data. The implementation is trivial:
change the old-fashioned static datum to an instance data member inside the
functionoid’’s this object, and poof, the data is not only thread-local, but
it is even safe with recursive calls: each call to yourCaller() will have
its own distinct Funct3 object with its own distinct instance data.

Note that we’’ve gained something without losing anything. If you want
thread-global data, functionoids can give you that too: just change it from
an instance data member inside the functionoid’’s this object to a static
data member within the functionoid’’s class, or even to a local-scope static
data. You’’d be no better off than with function-pointers, but you wouldn’’t
be worse off either.

The functionoid approach gives you a third option which is not available
with the old-fashioned approach: the functionoid lets callers decide whether
they want thread-local or thread-global data. They’’d be responsible to use
locks in cases where they wanted thread-global data, but at least they’’d
have the choice. It’’s easy:

void callerWithThreadLocalData()
{
...
Funct1 funct(...ctor-args...);
childRoutine(funct);
...
}

void callerWithThreadGlobalData()
{
...
static Funct1 funct(...ctor-args...); ? the static is the only
difference
childRoutine(funct);
...
}

Functionoids don’’t solve every problem encountered when making flexible
software, but they are strictly more powerful than function-pointers and
they are worth at least evaluating. In fact you can easily prove that
functionoids don’’t lose any power over function-pointers, since you can
imagine that the old-fashioned approach of function-pointers is equivalent
to having a global(!) functionoid object. Since you can always make a global
functionoid object, you haven’’t lost any ground. QED.

[ Top | Bottom | Previous section | Next section | Search the FAQ ]




Hello,
I was going through the Marshal Cline''s C++ FAQ-Lite.

I have a doubt regarding section 33.10.

Here he is declaring a pure virtual destructor in the base class.

And again defining it inline. Like this.

inline Funct::~Funct() { } // defined even though it''s pure virtual; it''s
faster this way; trust me

My doubt is:

1.. Why we should make destructors pure virtual. What is the advantage?
2.. If it is pure virtual, there should not any definition of it in the
base class. It should be only defined in derived class, so that we can
create the instance of derived class.
3.. But in this case it is not redefined in derived class. What is the
reason?
4.. Again how it is fasted to make it define inline in Base class? Because
inline request will not be obeyed by compiler, if it is virtual.
I am attaching the 33.10 section of FAQ for your reference.

[33.10] What the heck is a functionoid, and why would I use one?

Functionoids are functions on steroids. Functionoids are strictly more
powerful than functions, and that extra power solves some (not all) of the
challenges typically faced when you use function-pointers.

Let''s work an example showing a traditional use of function-pointers, then
we''ll translate that example into functionoids. The traditional
function-pointer idea is to have a bunch of compatible functions:

int funct1(...params...) { ...code... }
int funct2(...params...) { ...code... }
int funct3(...params...) { ...code... }

Then you access those by function-pointers:

typedef int(*FunctPtr)(...params...);

void myCode(FunctPtr f)
{
...
f(...args-go-here...);
...
}

Sometimes people create an array of these function-pointers:

FunctPtr array[10];
array[0] = funct1;
array[1] = funct1;
array[2] = funct3;
array[3] = funct2;
...

In which case they call the function by accessing the array:

array[i](...args-go-here...);

With functionoids, you first create a base class with a pure-virtual method:

class Funct {
public:
virtual int doit(int x) = 0;
virtual ~Funct() = 0;
};

inline Funct::~Funct() { } // defined even though it''s pure virtual; it''s
faster this way; trust me

Then instead of three functions, you create three derived classes:

class Funct1 : public Funct {
public:
virtual int doit(int x) { ...code from funct1... }
};

class Funct2 : public Funct {
public:
virtual int doit(int x) { ...code from funct2... }
};

class Funct3 : public Funct {
public:
virtual int doit(int x) { ...code from funct3... }
};

Then instead of passing a function-pointer, you pass a Funct*. I''ll create a
typedef called FunctPtr merely to make the rest of the code similar to the
old-fashioned approach:

typedef Funct* FunctPtr;

void myCode(FunctPtr f)
{
...
f->doit(...args-go-here...);
...
}

You can create an array of them in almost the same way:

FunctPtr array[10];
array[0] = new Funct1(...ctor-args...);
array[1] = new Funct1(...ctor-args...);
array[2] = new Funct3(...ctor-args...);
array[3] = new Funct2(...ctor-args...);
...

This gives us the first hint about where functionoids are strictly more
powerful than function-pointers: the fact that the functionoid approach has
arguments you can pass to the ctors (shown above as ...ctor-args...) whereas
the function-pointers version does not. Think of a functionoid object as a
freeze-dried function-call (emphasis on the word call). Unlike a pointer to
a function, a functionoid is (conceptually) a pointer to a partially called
function. Imagine for the moment a technology that lets you pass
some-but-not-all arguments to a function, then lets you freeze-dry that
(partially completed) call. Pretend that technology gives you back some sort
of magic pointer to that freeze-dried partially-completed function-call.
Then later you pass the remaining args using that pointer, and the system
magically takes your original args (that were freeze-dried), combines them
with any local variables that the function calculated prior to being
freeze-dried, combines all that with the newly passed args, and continues
the function''s execution where it left off when it was freeze-dried. That
might sound like science fiction, but it''s conceptually what functionoids
let you do. Plus they let you repeatedly "complete" that freeze-dried
function-call with various different "remaining parameters," as often as you
like. Plus they allow (not require) you to change the freeze-dried state
when it gets called, meaning functionoids can remember information from one
call to the next.

Okay, let''s get our feet back on the ground and we''ll work a couple of
examples to explain what all that mumbo jumbo really means.

Suppose the original functions (in the old-fashioned function-pointer style)
took slightly different parameters.

int funct1(int x, float y)
{ ...code... }

int funct2(int x, const std::string& y, int z)
{ ...code... }

int funct3(int x, const std::vector<double>& y)
{ ...code... }

When the parameters are different, the old-fashioned function-pointers
approach is difficult to use, since the caller doesn''t know which parameters
to pass (the caller merely has a pointer to the function, not the function''s
name or, when the parameters are different, the number and types of its
parameters) (do not write me an email about this; yes you can do it, but you
have to stand on your head and do messy things; but do not write me about
it - use functionoids instead).

With functionoids, the situation is, at least sometimes, much better. Since
a functionoid can be thought of as a freeze-dried function call, just take
the un-common args, such as the ones I''ve called y and/or z, and make them
args to the corresponding ctors. You may also pass the common args (in this
case the int called x) to the ctor, but you don''t have to - you have the
option of passing it/them to the pure virtual doit() method instead. I''ll
assume you want to pass x and into doit() and y and/or z into the ctors:

class Funct {
public:
virtual int doit(int x) = 0;
};

Then instead of three functions, you create three derived classes:

class Funct1 : public Funct {
public:
Funct1(float y) : y_(y) { }
virtual int doit(int x) { ...code from funct1... }
private:
float y_;
};

class Funct2 : public Funct {
public:
Funct2(const std::string& y, int z) : y_(y), z_(z) { }
virtual int doit(int x) { ...code from funct2... }
private:
std::string y_;
int z_;
};

class Funct3 : public Funct {
public:
Funct3(const std::vector<double>& y) : y_(y) { }
virtual int doit(int x) { ...code from funct3... }
private:
std::vector<double> y_;
};

Now you see that the ctor''s parameters get freeze-dried into the functionoid
when you create the array of functionoids:

FunctPtr array[10];

array[0] = new Funct1(3.14f);

array[1] = new Funct1(2.18f);

std::vector<double> bottlesOfBeerOnTheWall;
bottlesOfBeerOnTheWall.push_back(100);
bottlesOfBeerOnTheWall.push_back(99);
...
bottlesOfBeerOnTheWall.push_back(1);
array[2] = new Funct3(bottlesOfBeerOnTheWall);

array[3] = new Funct2("my string", 42);

...

So when the user invokes the doit() on one of these functionoids, he
supplies the "remaining" args, and the call conceptually combines the
original args passed to the ctor with those passed into the doit() method:

array[i]->doit(12);

As I''ve already hinted, one of the benefits of functionoids is that you can
have several instances of, say, Funct1 in your array, and those instances
can have different parameters freeze-dried into them. For example, array[0]
and array[1] are both of type Funct1, but the behavior of array[0]->doit(12)
will be different from the behavior of array[1]->doit(12) since the behavior
will depend on both the 12 that was passed to doit() and the args passed to
the ctors.

Another benefit of functionoids is apparent if we change the example from an
array of functionoids to a local functionoid. To set the stage, let''s go
back to the old-fashioned function-pointer approach, and imagine that you''re
trying to pass a comparison-function to a sort() or binarySearch() routine.
The sort() or binarySearch() routine is called childRoutine() and the
comparison function-pointer type is called FunctPtr:

void childRoutine(FunctPtr f)
{
...
f(...args...);
...
}

Then different callers would pass different function-pointers depending on
what they thought was best:

void myCaller()
{
...
childRoutine(funct1);
...
}

void yourCaller()
{
...
childRoutine(funct3);
...
}

We can easily translate this example into one using functionoids:

void childRoutine(Funct& f)
{
...
f.doit(...args...);
...
}

void myCaller()
{
...
Funct1 funct(...ctor-args...);
childRoutine(funct);
...
}

void yourCaller()
{
...
Funct3 funct(...ctor-args...);
childRoutine(funct);
...
}

Given this example as a backdrop, we can see two benefits of functionoids
over function-pointers. The "ctor args" benefit described above, plus the
fact that functionoids can maintain state between calls in a thread-safe
manner. With plain function-pointers, people normally maintain state between
calls via static data. However static data is not intrinsically
thread-safe - static data is shared between all threads. The functionoid
approach provides you with something that is intrinsically thread-safe since
the code ends up with thread-local data. The implementation is trivial:
change the old-fashioned static datum to an instance data member inside the
functionoid''s this object, and poof, the data is not only thread-local, but
it is even safe with recursive calls: each call to yourCaller() will have
its own distinct Funct3 object with its own distinct instance data.

Note that we''ve gained something without losing anything. If you want
thread-global data, functionoids can give you that too: just change it from
an instance data member inside the functionoid''s this object to a static
data member within the functionoid''s class, or even to a local-scope static
data. You''d be no better off than with function-pointers, but you wouldn''t
be worse off either.

The functionoid approach gives you a third option which is not available
with the old-fashioned approach: the functionoid lets callers decide whether
they want thread-local or thread-global data. They''d be responsible to use
locks in cases where they wanted thread-global data, but at least they''d
have the choice. It''s easy:

void callerWithThreadLocalData()
{
...
Funct1 funct(...ctor-args...);
childRoutine(funct);
...
}

void callerWithThreadGlobalData()
{
...
static Funct1 funct(...ctor-args...); ? the static is the only
difference
childRoutine(funct);
...
}

Functionoids don''t solve every problem encountered when making flexible
software, but they are strictly more powerful than function-pointers and
they are worth at least evaluating. In fact you can easily prove that
functionoids don''t lose any power over function-pointers, since you can
imagine that the old-fashioned approach of function-pointers is equivalent
to having a global(!) functionoid object. Since you can always make a global
functionoid object, you haven''t lost any ground. QED.

[ Top | Bottom | Previous section | Next section | Search the FAQ ]


解决方案

* santosh:


I was going through the Marshal Cline''s C++ FAQ-Lite.

I have a doubt regarding section 33.10.
That''s the one titled "What the heck is a functionoid, and why would I use
one?".

It would have been nice if you had included that title so we could know
what the, uhm, well, you were talking about! :-)

I have a doubt regarding the term "functionoid"; these small objects are
usually called "functors" in C++, and other things in other languages, and
I haven''t encountered the term "functionoid" anywhere else but in the FAQ.

Here he is declaring a pure virtual destructor in the base class.

And again defining it inline. Like this.

inline Funct::~Funct() { } // defined even though it''s pure virtual; it''s
faster this way; trust me
There seems to be no reason to declare the destructor in that class as
pure virtual; I don''t know why it is (editing blunder?).

My doubt is:

1.. Why we should make destructors pure virtual. What is the advantage?
If you have no other function at hand the destructor is a convenient one
to make pure virtual.

That makes the class effectively abstract at the language level.

It''s often done for so-called "marker interfaces".

2.. If it is pure virtual, there should not any definition of it in the
base class. It should be only defined in derived class, so that we can
create the instance of derived class.
No, that is incorrect.

Since the destructor is called there _must_ be a definition of it.

3.. But in this case it is not redefined in derived class. What is the
reason?
The compiler automatically generates a destructor if none is declared.

4.. Again how it is fasted to make it define inline in Base class? Because
inline request will not be obeyed by compiler, if it is virtual.
I think that comment about "faster" (as well as the destructor being pure
virtual) must be an editing blunder, perhaps something from old text about
something else.

I am attaching the 33.10 section of FAQ for your reference.



DON''T DO THAT!

Some people still read news over slow analog lines.

Please do be considerate; give an URL if necessary.

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?


* santosh:

Because
inline request will not be obeyed by compiler, if it is virtual.



Sorry, that is incorrect.

Not that ''inline'' is a request that the compiler is bound to obey
anyway.

But the compiler is free to inline calls of a virtual function, and
if it''s a good compiler, it will, where appropriate.

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?


> My doubt is:


1.. Why we should make destructors pure virtual. What is the advantage?
Pure virtual functions are used to make classes abstract. One can make
a destructor pure virtual when a class may not have any other candidate
member functions that can be made pure virtual. In such a case, instead
of having a dummy pure virtual function, you can make the destructor
pure virtual.
2.. If it is pure virtual, there should not any definition of it in the
base class. It should be only defined in derived class, so that we can
create the instance of derived class.
Pure virtual destructors _have_ to have a definition. This is because,
in an inheritance heirarchy while destroying a derived class object,
all the destructors in the class heirarchy are called. If you could
leave out the definition of the pure virtual destructor, then there
would be no destructor function body to call.
3.. But in this case it is not redefined in derived class. What is the
reason?
Pure virtual destructors need not be defined in the derived class. The
reason is this. Normal pure virtual functions in a base class would
cause the derived class to be abstract unless it is defined in the
derived class. But destructors are automatically supplied by the
compiler if you don''t give one yourself.
4.. Again how it is fasted to make it define inline in Base class? Because
inline request will not be obeyed by compiler, if it is virtual.
I''m not sure about how it would be faster.

Regards,
Srini
santosh wrote: Hello,
I was going through the Marshal Cline''s C++ FAQ-Lite.

I have a doubt regarding section 33.10.

Here he is declaring a pure virtual destructor in the base class.

And again defining it inline. Like this.

inline Funct::~Funct() { } // defined even though it''s pure virtual; it''s
faster this way; trust me

My doubt is:

1.. Why we should make destructors pure virtual. What is the advantage?
2.. If it is pure virtual, there should not any definition of it in the
base class. It should be only defined in derived class, so that we can
create the instance of derived class.
3.. But in this case it is not redefined in derived class. What is the
reason?
4.. Again how it is fasted to make it define inline in Base class? Because
inline request will not be obeyed by compiler, if it is virtual.
I am attaching the 33.10 section of FAQ for your reference.

[33.10] What the heck is a functionoid, and why would I use one?

Functionoids are functions on steroids. Functionoids are strictly more
powerful than functions, and that extra power solves some (not all) of the
challenges typically faced when you use function-pointers.

Let''s work an example showing a traditional use of function-pointers, then
we''ll translate that example into functionoids. The traditional
function-pointer idea is to have a bunch of compatible functions:

int funct1(...params...) { ...code... }
int funct2(...params...) { ...code... }
int funct3(...params...) { ...code... }

Then you access those by function-pointers:

typedef int(*FunctPtr)(...params...);

void myCode(FunctPtr f)
{
...
f(...args-go-here...);
...
}

Sometimes people create an array of these function-pointers:

FunctPtr array[10];
array[0] = funct1;
array[1] = funct1;
array[2] = funct3;
array[3] = funct2;
...

In which case they call the function by accessing the array:

array[i](...args-go-here...);

With functionoids, you first create a base class with a pure-virtual method:

class Funct {
public:
virtual int doit(int x) = 0;
virtual ~Funct() = 0;
};

inline Funct::~Funct() { } // defined even though it''s pure virtual; it''s
faster this way; trust me

Then instead of three functions, you create three derived classes:

class Funct1 : public Funct {
public:
virtual int doit(int x) { ...code from funct1... }
};

class Funct2 : public Funct {
public:
virtual int doit(int x) { ...code from funct2... }
};

class Funct3 : public Funct {
public:
virtual int doit(int x) { ...code from funct3... }
};

Then instead of passing a function-pointer, you pass a Funct*. I''ll create a
typedef called FunctPtr merely to make the rest of the code similar to the
old-fashioned approach:

typedef Funct* FunctPtr;

void myCode(FunctPtr f)
{
...
f->doit(...args-go-here...);
...
}

You can create an array of them in almost the same way:

FunctPtr array[10];
array[0] = new Funct1(...ctor-args...);
array[1] = new Funct1(...ctor-args...);
array[2] = new Funct3(...ctor-args...);
array[3] = new Funct2(...ctor-args...);
...

This gives us the first hint about where functionoids are strictly more
powerful than function-pointers: the fact that the functionoid approach has
arguments you can pass to the ctors (shown above as ...ctor-args...) whereas
the function-pointers version does not. Think of a functionoid object as a
freeze-dried function-call (emphasis on the word call). Unlike a pointer to
a function, a functionoid is (conceptually) a pointer to a partially called
function. Imagine for the moment a technology that lets you pass
some-but-not-all arguments to a function, then lets you freeze-dry that
(partially completed) call. Pretend that technology gives you back some sort
of magic pointer to that freeze-dried partially-completed function-call.
Then later you pass the remaining args using that pointer, and the system
magically takes your original args (that were freeze-dried), combines them
with any local variables that the function calculated prior to being
freeze-dried, combines all that with the newly passed args, and continues
the function''s execution where it left off when it was freeze-dried. That
might sound like science fiction, but it''s conceptually what functionoids
let you do. Plus they let you repeatedly "complete" that freeze-dried
function-call with various different "remaining parameters," as often as you
like. Plus they allow (not require) you to change the freeze-dried state
when it gets called, meaning functionoids can remember information from one
call to the next.

Okay, let''s get our feet back on the ground and we''ll work a couple of
examples to explain what all that mumbo jumbo really means.

Suppose the original functions (in the old-fashioned function-pointer style)
took slightly different parameters.

int funct1(int x, float y)
{ ...code... }

int funct2(int x, const std::string& y, int z)
{ ...code... }

int funct3(int x, const std::vector<double>& y)
{ ...code... }

When the parameters are different, the old-fashioned function-pointers
approach is difficult to use, since the caller doesn''t know which parameters
to pass (the caller merely has a pointer to the function, not the function''s
name or, when the parameters are different, the number and types of its
parameters) (do not write me an email about this; yes you can do it, but you
have to stand on your head and do messy things; but do not write me about
it - use functionoids instead).

With functionoids, the situation is, at least sometimes, much better. Since
a functionoid can be thought of as a freeze-dried function call, just take
the un-common args, such as the ones I''ve called y and/or z, and make them
args to the corresponding ctors. You may also pass the common args (in this
case the int called x) to the ctor, but you don''t have to - you have the
option of passing it/them to the pure virtual doit() method instead. I''ll
assume you want to pass x and into doit() and y and/or z into the ctors:

class Funct {
public:
virtual int doit(int x) = 0;
};

Then instead of three functions, you create three derived classes:

class Funct1 : public Funct {
public:
Funct1(float y) : y_(y) { }
virtual int doit(int x) { ...code from funct1... }
private:
float y_;
};

class Funct2 : public Funct {
public:
Funct2(const std::string& y, int z) : y_(y), z_(z) { }
virtual int doit(int x) { ...code from funct2... }
private:
std::string y_;
int z_;
};

class Funct3 : public Funct {
public:
Funct3(const std::vector<double>& y) : y_(y) { }
virtual int doit(int x) { ...code from funct3... }
private:
std::vector<double> y_;
};

Now you see that the ctor''s parameters get freeze-dried into the functionoid
when you create the array of functionoids:

FunctPtr array[10];

array[0] = new Funct1(3.14f);

array[1] = new Funct1(2.18f);

std::vector<double> bottlesOfBeerOnTheWall;
bottlesOfBeerOnTheWall.push_back(100);
bottlesOfBeerOnTheWall.push_back(99);
...
bottlesOfBeerOnTheWall.push_back(1);
array[2] = new Funct3(bottlesOfBeerOnTheWall);

array[3] = new Funct2("my string", 42);

...

So when the user invokes the doit() on one of these functionoids, he
supplies the "remaining" args, and the call conceptually combines the
original args passed to the ctor with those passed into the doit() method:

array[i]->doit(12);

As I''ve already hinted, one of the benefits of functionoids is that you can
have several instances of, say, Funct1 in your array, and those instances
can have different parameters freeze-dried into them. For example, array[0]
and array[1] are both of type Funct1, but the behavior of array[0]->doit(12)
will be different from the behavior of array[1]->doit(12) since the behavior
will depend on both the 12 that was passed to doit() and the args passed to
the ctors.

Another benefit of functionoids is apparent if we change the example from an
array of functionoids to a local functionoid. To set the stage, let''s go
back to the old-fashioned function-pointer approach, and imagine that you''re
trying to pass a comparison-function to a sort() or binarySearch() routine.
The sort() or binarySearch() routine is called childRoutine() and the
comparison function-pointer type is called FunctPtr:

void childRoutine(FunctPtr f)
{
...
f(...args...);
...
}

Then different callers would pass different function-pointers depending on
what they thought was best:

void myCaller()
{
...
childRoutine(funct1);
...
}

void yourCaller()
{
...
childRoutine(funct3);
...
}

We can easily translate this example into one using functionoids:

void childRoutine(Funct& f)
{
...
f.doit(...args...);
...
}

void myCaller()
{
...
Funct1 funct(...ctor-args...);
childRoutine(funct);
...
}

void yourCaller()
{
...
Funct3 funct(...ctor-args...);
childRoutine(funct);
...
}

Given this example as a backdrop, we can see two benefits of functionoids
over function-pointers. The "ctor args" benefit described above, plus the
fact that functionoids can maintain state between calls in a thread-safe
manner. With plain function-pointers, people normally maintain state between
calls via static data. However static data is not intrinsically
thread-safe - static data is shared between all threads. The functionoid
approach provides you with something that is intrinsically thread-safe since
the code ends up with thread-local data. The implementation is trivial:
change the old-fashioned static datum to an instance data member inside the
functionoid''s this object, and poof, the data is not only thread-local, but
it is even safe with recursive calls: each call to yourCaller() will have
its own distinct Funct3 object with its own distinct instance data.

Note that we''ve gained something without losing anything. If you want
thread-global data, functionoids can give you that too: just change it from
an instance data member inside the functionoid''s this object to a static
data member within the functionoid''s class, or even to a local-scope static
data. You''d be no better off than with function-pointers, but you wouldn''t
be worse off either.

The functionoid approach gives you a third option which is not available
with the old-fashioned approach: the functionoid lets callers decide whether
they want thread-local or thread-global data. They''d be responsible to use
locks in cases where they wanted thread-global data, but at least they''d
have the choice. It''s easy:

void callerWithThreadLocalData()
{
...
Funct1 funct(...ctor-args...);
childRoutine(funct);
...
}

void callerWithThreadGlobalData()
{
...
static Funct1 funct(...ctor-args...); ? the static is the only
difference
childRoutine(funct);
...
}

Functionoids don''t solve every problem encountered when making flexible
software, but they are strictly more powerful than function-pointers and
they are worth at least evaluating. In fact you can easily prove that
functionoids don''t lose any power over function-pointers, since you can
imagine that the old-fashioned approach of function-pointers is equivalent
to having a global(!) functionoid object. Since you can always make a global
functionoid object, you haven''t lost any ground. QED.

[ Top | Bottom | Previous section | Next section | Search the FAQ ]




这篇关于纯虚拟析构函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆