`
happmaoo
  • 浏览: 4333479 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

C++ virtual member function FAQ

阅读更多

【1】 虚成员函数和非虚成员函数调用方式有什么不同?
非虚成员函数是静态确定的。也就是说,该成员函数(在编译时)被静态地选择,该选择基于指向对象的指针(或引用)的类型。 相比而言,虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类 型。这被称作“动态绑定/动态联编”。大多数的编译器使用以下的一些的技术,也就是所谓的“VTABLE”机制:
编译器发现一个类中有被声明为virtual的函数,就会为其搞一个虚函数表,也就是VTABLE。VTABLE实际上是一个函数指针的数组,每个虚函 数占用这个数组的一个slot。一个类只有一个VTABLE,不管它有多少个实例。派生类有自己的VTABLE,但是派生类的VTABLE与基类的 VTABLE有相同的函数排列顺序,同名的虚函数被放在两个数组的相同位置上。在创建类实例的时候,编译器还会在每个实例的内存布局中增加一个vptr字 段,该字段指向本类的VTABLE。通过这些手段,编译器在看到一个虚函数调用的时候,就会将这个调用改写,在分发一个虚函数时,运行时系统跟随对象的 vptr找到类的vtbl,然后跟随vtbl中适当的项找到方法的代码。
以上技术的空间开销是存在的:每个对象一个额外的指针(仅仅对于需要动态绑定的对象),加上每个方法一个额外的指针(仅仅对于虚方法)。时间开销也是有 的:和普通函数调用比较,虚函数调用需要两个额外的步骤(得到vptr的值,得到方法的地址)。由于编译器在编译时就通过指针类型解决了非虚函数的调用, 所以这些开销不会发生在非虚函数上。
下面代码演示了如何通过获取虚函数指针来调用虚函数的例子:
class Base
{
int a;
public:
virtual void fun1() {cout<<"Base::fun1()"<<endl;}
virtual void fun2() {cout<<"Base::fun2()"<<endl;}
virtual void fun3() {cout<<"Base::fun3()"<<endl;}
};

class A : public Base
{
int a;
public:
virtual void fun1() {cout<<"A::fun1()"<<endl;}
virtual void fun2() {cout<<"A::fun2()"<<endl;}
};

void *getp (void* p)
{
return (void*) *(unsigned long*)p; // 获取对象内存中的虚函数表地址,即vptr指向的内容
}

fun getfun (Base* obj, unsigned long off)
{
void *vptr = getp(obj);

unsigned char *p = (unsigned char *)vptr;
p += sizeof(void*)*off;// 按字节数加上指针偏移,找到存储虚函数地址的内存位置

return (fun)getp(p); // 去虚函数表中当前位置的内容,即虚函数在内存中的地址
}

int main()
{
Demo::A a;
Demo::Base *p = &a;

fun f = getfun(p, 0);
(*f)();
f = getfun(p, 1);
(*f)();
f = getfun(p, 2);
(*f)();

return 0;
}
注意:上面示例在vs2003下编译通 过,其通过偏移获取虚函数表地址,进而获取虚函数地址,是基于vs下对象的内存布局是先虚函数表指针,然后成员变量的,也就是说指向虚函数表的指针被放置 在对象内存的最前面。gcc下的情况有所不同,指向虚函数的指针是放在成员变量后面的。

【2】 析构函数也可以是虚的,甚至是纯虚的,但是构造函数不能是虚的
纯虚的析构函数并没有什么作用,是虚的就够了。通常只有在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。构造函数不能是虚的(为什么?因为在一个构造函数调用期间,虚机制并不工作),但是你可以可能通过虚函数virtual clone()(对于拷贝构造函数)或虚函数 virtual create()(对于默认构造函数),得到虚构造函数产生的效果。如下:
classShape{
public:
virtual~Shape(){}//虚析构函数
virtualvoiddraw()=0;
//纯虚函数
virtualvoidmove()=0;
//...
virtualShape*clone()const=0;
//使用拷贝构造函数
virtualShape*create()const=0;
//使用默认构造函数
};

classCircle:publicShape{
public:
Circle*clone()const{returnnewCircle(*this);}
Circle*create()const{returnnewCircle();}
//...
};
clone()成员函数中,代码newCircle(*this) 调用Circle 的拷贝构造函数来复制this的状态到新创建的Circle对象。在create()成员函数中,代码newCircle()调用Circle的默认构造函数。
用户将它们看作“虚构造函数”来使用它们:
voiduserCode(Shape&s)
{
Shape*s2=s.clone();
Shape*s3=s.create();
//...
deletes2;
//在此处,你可能需要虚析构函数
deletes3;
}
这个函数将正确工作,而不管Shape是一个CircleSquare,或是其他种类的Shape,甚至它们还并不存在。

【3】 构造函数和析构函数中的虚函数调用
一个类的虚函数在它自己的构造函数和析构函数中被调用的时候,它们就变成普通函数了,不“虚”了。也就是说不能在构造函数和析构函数中让自己“多态”。例如:
class A
{
public:
A() { foo();} // 在这里,无论如何都是A::foo()被调用!
~A() { foo();} // 同上
virtual void foo();
};

class B: public A
{
public:
virtual void foo();
};

void bar()
{
A * a = new B;
delete a;
}
如果你希望delete a的时候,会导致B::foo()被调用,那么你就错了。同样,在new B的时候,A的构造函数被调用,但是在A的构造函数中,被调用的是A::foo()而不是B::foo()。为什么会有这样的规定呢,原因如下:
当基类被构造时,对象还不是一个派生类的对象,所以如果Base::Base()调用了虚函数 virt(),则Base::virt()将被调用,即使Derived::virt()(派生类重写该虚函数)存在。
同样,当基类被析构时,对象已经不再是一个派生类对象了,所以如果Base::~Base()调用了virt(),则Base::virt()得到控制权,而不是重写的Derived::virt()
当你可以想象到如果Derived::virt()涉及到派生类的某个成员对象将造成的灾难的时候,你很快就能看到这种方法的明智。详细来说,如果Base::Base()调用了虚函数virt(),这个规则使得Base::virt()被调用。如果不按照这个规则,Derived::virt()将在派生对象的派生部分被构造之前被调用,此时属于派生对象的派生部分的某个成员对象还没有被构造,而Derived::virt()却能够访问它。这将是灾难。

【4】私有private的虚函数是否具有多态性
考虑下面的例子:
class A
{
public:
void foo() { bar();}
private:
virtual void bar() { ...}
};

class B: public A
{
private:
virtual void bar() { ...}
};
在这个例子中,虽然bar()在A类中是private的,但是仍然可以出现在派生类中,并仍然可以与public或者protected的虚函数一样产 生多态的效果。并不会因为它是private的,就发生A::foo()不能访问B::bar()的情况,也不会发生B::bar()对A::bar() 的override不起作用的情况。
这种写法的语意是:A告诉B,你最好override我的bar()函数,但是你不要管它如何使用,也不要自己调用这个函数。

Copyright@戴维 2006.4于北京

分享到:
评论

相关推荐

    C++中的虚函数(virtual function).doc

    C++中的虚函数(virtual function).doc virtual function

    C++之父Bjarne Stroustrup 的C++ Style and Technique FAQ

    C++之父Bjarne Stroustrup 的C++ Style and Technique FAQ

    C/C++语言初学者FAQ

    C/C++语言初学者FAQ C/C++语言初学者FAQ

    The implementation mechanism for C++ virtual function

    C++这门语言是一门比较复杂的语言,光从这门语言本身去学习是不够的。 要想更好的使用C++这门编程语言,我们需要了解C++后面的一些东西。 功能点很多,泛泛而谈恐怕效果不是太好。 通过编写程序及使用工具,从另一...

    C++ FAQ Lite

    CFAQ的C++版本,相信CFAQ对C语言学习很有帮助,C++FAQ应该对C++学习者也很有帮助

    C++中的虚函数(virtual function)

    C++中的虚函数(virtual function) 1.简介 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。假设我们有下面的类层次: class A { public: virtual void foo() { cout ...

    c++11FAQ中文版

    cpp-11-faq.pdf c++11FAQ中文版 167页文字版带目录 需要的同学赶紧拿走

    C++析构函数使用virtual的原因

    C++析构函数使用virtual的原因

    C++中的virtuals

    C++中virtual知识点,全英文PPT 对于C++的初学者来说是非常好的资源,特别是正在学习外文教材的同学更有意义

    C++ FAQ中文版.chm

    C++ FAQ中文版.chm

    C++中的虚函数(virtual function).rar_C++虚函数_虚函数

    C++中的虚函数(virtual function)

    c++faq ,讨论了关于c++的一些基本问题。英文。

    c++faq:即c++经常被问到的问题。是关于c++一些基本问题的讨论,对于刚学c++的初学者了解c++的基本知识是很有帮助的。英文。

    C++C++C++C++C++C++C++

    C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++C++

    C++中的Virtual Function (虚函数)

    1.C++ Virtual 用法  这里只讲语法,因为讲原理比较难。还没有涉及到构造函数。那么直接上代码了: // VitualFunction.cpp : Defines the entry point for the console application. // #include "stdafx.h" #...

    C++部分问题FAQ

    C++部分问题FAQ

    c++ function

    c++常用函数c++常用函数c++常用函数c++常用函数汇总

    C++ 之父的 FAQ .doc格式

    文件 .doc 格式,方便编辑,打印 内容: C++ 风格与技术 FAQ 普通 FAQ 两部分,共100个文件 有空多去去 http://meooo.download.csdn.net/ 也许有你喜欢东西和书籍......

    编程及C/C++初学者 FAQ

    本文原为本人在论坛所发若干帖,意在集中解决新手学习C/C++语言时将遭遇到的各类问题,网友反馈情况良好,集合修订后作为个人作品贴于此处。 本贴主要分析概念原理和解决方案,不讨论具体程序语法,立足于让初学者能...

    浅谈C++中virtual的三种用法

    virtual用法一 #include using namespace std; class A{ public: virtual void display(){ cout&lt;&lt;"A"&lt;&lt;ENDL&gt;display(); delete p; } int main(int argc,char* argv[]) { doDisplay(new B()); return 0; ...

    深入理解c++中virtual关键字

    c++中的函数调用默认不适用动态绑定。要触发动态绑定,必须满足两个条件:第一,指定为虚函数;第二,通过基类类型的引用或指针调用。由此可见,virtual主要主要是实现动态绑定。 2.那些情况下可以使用virtual关键字...

Global site tag (gtag.js) - Google Analytics