当前位置:首页 » 编程软件 » 虚基类的函数编译吗

虚基类的函数编译吗

发布时间: 2022-07-16 11:45:15

① 请教关于C++中虚基类的一个问题

这是个菱形继承,菱形继承
即是
子类有两个父类,这两个父类都继承自同一个类。这时候,如果不使用
虚基类

定义
子类的时候,会生成两个“祖父”基类的样本,在调用“祖父”基类的成员函数的时候,就会出现“歧义”错误,无法编译通过。解决的办法就是使用虚基类,这样就只保留一个“祖父”基类的样本,调用也不会出现“歧义”了。
如上面例子,不使用虚基类,在“祖父”基类Base中定义一个show1
函数
,在类C中调用就会出现问题:
#include
<iostream>
using
namespace
std
;
class
Base{
public:
void
show(){cout<<"Base
class"<<endl;}
void
show1(){cout<<"Base
class
1"<<endl;}
};
class
A:
public
Base{
public:
void
show(){cout<<"Class
A"<<endl;}
};
class
B:
public
Base{
public:
void
show(){cout<<"Class
B"<<endl;}
};
class
C:
public
A,public
B{
public:
void
show(){cout<<"Class
C"<<endl;}
};
int
main(){
C
c;
c.show();
c.show1();
return
0;
}
报错信息如下:
error
C2385:
'C::show1'
is
ambiguous
warning
C4385:
could
be
the
'show1'
in
base
'Base'
of
base
'A'
of
class
'C'
warning
C4385:
or
the
'show1'
in
base
'Base'
of
base
'B'
of
class
'C'
改成虚基类就
没问题
了。

② 虚基类和非虚基类在编写方法上有什么不同,是不是只是多了一个 virtual

您好,C++编译器们必须实现语言的每一个特性。这些实现的细节当然是由编译器来决定的,并且不同的编译器有不同的方法实现语言的特性。在多数情况下,你不用关心这些事情。然而有些特性的实现对对象大小和其成员函数执行速度有很大的影响,所以对于这些特性有一个基本的了解,知道编译器可能在背后做了些什么,就显得很重要。这种特性中最重要的例子是虚拟函数。
当调用一个虚拟函数时,被执行的代码必须与调用函数的对象的动态类型相一致;指向对象的指针或引用的类型是不重要的。编译器如何能够高效地提供这种行为呢?大多数编译器是使用virtual table和virtual table pointers。virtual table和virtual table pointers通常被分别地称为vtbl和vptr。
一个vtbl通常是一个函数指针数组。(一些编译器使用链表来代替数组,但是基本方法是一样的)在程序中的每个类只要声明了虚函数或继承了虚函数,它就有自己的vtbl,并且类中vtbl的项目是指向虚函数实现体的指针。例如,如下这个类定义:
class C1 {
public:
C1();
virtual ~C1();
virtual void f1();
virtual int f2(char c) const;
virtual void f3(const string& s);
void f4() const;
...
};
C1的virtual table数组看起来如下图所示:

注意非虚函数f4不在表中,而且C1的构造函数也不在。非虚函数(包括构造函数,它也被定义为非虚函数)就象普通的C函数那样被实现,所以有关它们的使用在性能上没有特殊的考虑。
如果有一个C2类继承自C1,重新定义了它继承的一些虚函数,并加入了它自己的一些虚函数,
class C2: public C1 {
public:
C2(); // 非虚函数
virtual ~C2(); // 重定义函数
virtual void f1(); // 重定义函数
virtual void f5(char *str); // 新的虚函数
...
};
它的virtual table项目指向与对象相适合的函数。这些项目包括指向没有被C2重定义的C1虚函数的指针:

这个论述引出了虚函数所需的第一个代价:你必须为每个包含虚函数的类的virtual talbe留出空间。类的vtbl的大小与类中声明的虚函数的数量成正比(包括从基类继承的虚函数)。每个类应该只有一个virtual table,所以virtual table所需的空间不会太大,但是如果你有大量的类或者在每个类中有大量的虚函数,你会发现vtbl会占用大量的地址空间。
因为在程序里每个类只需要一个vtbl拷贝,所以编译器肯定会遇到一个棘手的问题:把它放在哪里。大多数程序和程序库由多个object(目标)文件连接而成,但是每个object文件之间是独立的。哪个object文件应该包含给定类的vtbl呢?你可能会认为放在包含main函数的object文件里,但是程序库没有main,而且无论如何包含main的源文件不会涉及很多需要vtbl的类。编译器如何知道它们被要求建立那一个vtbl呢?
必须采取一种不同的方法,编译器厂商为此分成两个阵营。对于提供集成开发环境(包含编译程序和连接程序)的厂商,一种干脆的方法是为每一个可能需要vtbl的object文件生成一个vtbl拷贝。连接程序然后去除重复的拷贝,在最后的可执行文件或程序库里就为每个vtbl保留一个实例。
更普通的设计方法是采用启发式算法来决定哪一个object文件应该包含类的vtbl。通常启发式算法是这样的:要在一个object文件中生成一个类的vtbl,要求该object文件包含该类的第一个非内联、非纯虚拟函数(non-inline non-pure virual function)定义(也就是类的实现体)。因此上述C1类的vtbl将被放置到包含C1::~C1定义的object文件里(不是内联的函数),C2类的vtbl被放置到包含C1::~C2定义的object文件里(不是内联函数)。
实际当中,这种启发式算法效果很好。但是如果你过分喜欢声明虚函数为内联函数(参见Effective C++条款33),如果在类中的所有虚函数都内声明为内联函数,启发式算法就会失败,大多数基于启发式算法的编译器会在每个使用它的object文件中生成一个类的vtbl。在大型系统里,这会导致程序包含同一个类的成百上千个vtbl拷贝!大多数遵循这种启发式算法的编译器会给你一些方法来人工控制vtbl的生成,但是一种更好的解决此问题的方法是避免把虚函数声明为内联函数。下面我们将看到,有一些原因导致现在的编译器一般总是忽略虚函数的的inline指令。

③ 虚基类的一个问题

访问的变量或函数声明在派生类上,应改为派生类的指针(派生类 *)
这是因为,基类并不知道派生类增加了什么内容。实际上就是,编译器不能根据基类指针来判断派生类中新增变量的位置和新增函数的入口点。若访问的函数本来就在基类中声明过的(包括虚函数和纯虚函数),就不必要修改。
要访问派生类重写的函数(函数名相同,但不是虚函数),应改为派生类的指针。
大概这些吧。

④ 虚继承的虚继承与虚基类的本质

虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系之间的巨大的差异(包括访问性能上的差异),现在我们就来彻底的从语言、模型、性能和应用等多个方面对虚继承和虚基类进行研究。
首先还是先给出虚继承和虚基类的定义。
虚继承:在继承定义中包含了virtual关键字的继承关系;
虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
struct CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass
的虚基类,而不是说CBase就是个虚基类,因为CBase还可以作为不是虚继承体系中的基类。
有了上面的定义后,就可以开始虚继承和虚基类的本质研究了,下面按照语法、语义、模型、性能和应用五个方面进行全面的描述。 语法有语言的本身的定义所决定,总体上来说非常的简单,如下:
struct CSubClass : public virtual CBaseClass {};
其中可以采用public、protected、private三种不同的继承关键字进行修饰,只要确保包含virtual就可以了,这样一来就形成了虚继承体系,同时CBaseClass就成为了CSubClass的虚基类了。
其实并没有那么的简单,如果出现虚继承体系的进一步继承会出现什么样的状况呢?
如下所示:
/*
* 带有数据成员的基类
*/
struct CBaseClass1
{
CBaseClass1( size_t i ) : m_val( i ) {}
size_t m_val;
};
/*
* 虚拟继承体系
*/
struct CSubClassV1 : public virtual CBaseClass1
{
CSubClassV1( size_t i ) : CBaseClass1( i ) {}
};
struct CSubClassV2 : public virtual CBaseClass1
{
CSubClassV2( size_t i ) : CBaseClass1( i ) {}
};
struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
{
CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
};
struct CDiamondSubClass1 : public CDiamondClass1
{
CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
};
注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作,如果没有这个初始化语句就会导致编译时错误,为什么会这样呢?一般情况下不是只要在CSubClassV1和CSubClassV2中包含初始化就可以了么?要解释该问题必须要明白虚继承的语义特征,所以参看下面语义部分的解释。 从语义上来讲什么是虚继承和虚基类呢?上面仅仅是从如何在C++语言中书写合法的虚继承类定义而已。首先来了解一下virtual这个关键字在C++中的公共含义,在C++语言中仅仅有两个地方可以使用virtual这个关键字,一个就是类成员虚函数和这里所讨论的虚继承。不要看这两种应用场合好像没什么关系,其实他们在背景语义上具有virtual这个词所代表的共同的含义,所以才会在这两种场合使用相同的关键字。
那么virtual这个词的含义是什么呢?
virtual在《美国传统词典[双解]》中是这样定义的:
adj.(形容词)
1. Existing or resulting in essence or effect though not in actual fact, form, or name:
实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效果上存在或产生的;
2. Existing in the mind, especially as a proct of the imagination. Used in literary criticism of text.
虚的,内心的:在头脑中存在的,尤指意想的产物。用于文学批评中。
我们采用第一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的,但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段才能够体现出其实际上的效果。
那么在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种体制。存在,但间接。其中关键就在于存在、间接和共享这三种特征。
对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。
对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型中会讲到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。
那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必须在每一个继承类中都必须包含虚基类的初始化语句”。由上面的分析可以知道,
虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。 为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚继承下的内存模型。
3.1. 存在
也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,虚基类的子对象都会被放置在对象内存的尾部)
3.2. 间接
间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型的值)
3.3. 共享
共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,体现这些内容。 由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。
4.1. 时间
在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。(在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov常量直接寻址语句)
4.2. 空间
由于共享所以不存在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。

⑤ C++ 虚基类的问题。。。

c.A::x = 10; 可以这样修改。
编译的提示错误是 x数据成员不明确,警告是,无法判断是类A或类B的数据成员。
因为你虚继承的2个类中,数据成员的变量名是相同的,使用时需加上类操作符限定。

⑥ C++虚基类问题

你所说的车这个“虚基类”并不是一个实现类,也不是类的实例。
其实你可以这么理解:
汽车、自行车、摩托车这几个类之间并没有什么联系,是完全不同的类。只不过有相同的属性而已,例如:汽车有汽车的重量和自行车有自行车的重量,而车是凌驾于“各种车们”之上的概念,它其实是与汽车、自行车、摩托车类一样,都是类。他们唯一的关系是:定义汽车的重量时用到了车的重量这个概念,都继承于车,证明他们都有车的共同属性,但各自的重量可以不同,汽车是1T而自行车是2kg。
总体来说就是,这些汽车、自行车、**车...他们都有这些属性,但属性值可以不同!
上升点高度:子类继承于父类后,会继承父类的属性,而不会继承父类的属性值,同样,各子类间的属性值没有任何关系。

不知道讲明白没~~

⑦ C++虚基类 问题

虚基类是相对于它的派生类而言的,它本身可以是一个普通的类。
只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类b虚继承于类a,那类a就称作类b的虚基类,如果没有虚继承,那类b就只是类a的基类。
虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。
例如
由类a派生类b和类c,类d又同时继承类b和类c,这时候类d就要用虚继承的方式避免重复继承类a两次。

⑧ 虚基类在解决二义性中的问题中的作用是什么

虚基类用于某类从多个类继承,这多个基类有共同基类时,这个最上层基类的成员会多次在最终派生类出现而产生二义性,为避免二义性,使得最终派生类中,最上层的基类成员只有一份,这时需要虚拟继承,该最上层类就是虚基类,需要注意的是,该类在第一层派生时就要虚拟继承才行,使用方法是在继承方式前加上一个 virtual就可以了。

找个程序给你看一下:

1.为解决多继承产生的二义性问题,可以使用虚基类来消除二义性问题、如:
//在最后的派生类中,不仅要负责对直接基类进行初始化,还要负责对虚基类进行初始化
//编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类对虚基类的构造函数的调用,这就保证了虚基类的//数据成员不会被多次初始化、
#include<iostream>
#include<string>
using namespace std;
class person
{
protected:
string name;
char sex;
int age;
public:
person(string nam,char s,int a):name(nam),sex(s),age(a)
{ }
};
class teacher:virtual public person
{
protected:
string title;
public:
teacher(string nam,char s,int a,string t):person(nam,s,a),title(t)
{ }
};
class student:virtual public person
{
protected:
float score;
public:
student(string nam,char s,int a,float sco):person(nam,s,a),score(sco)
{ }

};
class Graate:public teacher,public student
{
private:
float wawg;
public:
Graate(string nam,char s,int a,string t,float sco,float w):person(nam,s,a),teacher(nam,s,a,t),student(nam,s,a,sco),wawg(w){}
void show()
{
cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"score:"<<score<<endl;
cout<<"title:"<<title<<endl;
cout<<"wawgs:"<<wawg<<endl;

}
};
int main()
{
Graate grad1("wang_li",'f',24,"assistent",89.5,1234.5);
grad1.show();
return 0;
}
2.利用一个指向基类的指针指向派生类的对象,看一下这个例子你就懂了。
#include<iostream>
#include<string>
using namespace std;
class student
{
private:
int num;
string name;
float score;
public:
student(int,string,float);
void display();
};
student::student(int n,string nam,float s):num(n),name(nam),score(s)
{}
void student::display()
{
cout<<"student的display(函数)"<<endl;
cout<<endl<<"num : "<<num<<endl;
cout<<"name: "<<name<<endl;
cout<<"score: "<<score<<endl;
}
class Graate:public student
{
private:
float pay;
public:
Graate(int,string,float,float);
void display();
};
Graate::Graate(int n,string nam,float s,float p):student(n,nam,s),pay(p)
{}

void Graate::display()
{
cout<<"Graate的dispaay(函数)"<<endl;
student::display();
cout<<"pay="<<pay<<endl;
}
int main()
{
student stu1(1001,"li",87.5);
Graate grad1(2001,"wang",98.7,7865.4);
student *pt=&stu1;
pt->display();
cout<<"============================"<<endl;
pt=&grad1;
pt->display();

cout<<"============================"<<endl;
stu1=grad1;
stu1.display();
return 0;
}
在student 的void display();前面加上virtual在试试,你就会明白虚函数的作用。

⑨ A是B的虚基类,B是C的虚基类吗

其实 不要管虚基类的定义说的有多么复杂

记住 判断是否为虚基类的 只有一个标准
就是继承的时候 是不是有virtual

没有 就不是

所以 B不是C的虚基类。

作为证明 其实也很简单。
在B里面写一个成员。
然后 写个C:public B
写个D:public B
最后写一个E:public C,public D

编译会出错 说明不是虚基类。

⑩ 什么叫做虚基类,它有何作用

在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。

虚继承是面向对象编程中的一种技术,是指一个指定的基类,在继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。

虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。

(10)虚基类的函数编译吗扩展阅读

使用虚基类注意事项

(1) 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。

(2) 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。

(3) 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。

(4) 最远派生类是指在继承结构中建立对象时所指定的类。

(5) 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。

(6) 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。

(7) 在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。

热点内容
夸克编程 发布:2025-02-07 21:43:43 浏览:526
电源450适合哪些配置 发布:2025-02-07 21:25:24 浏览:429
微信密码一般要多少位以上 发布:2025-02-07 21:24:19 浏览:877
sqldecimal转换 发布:2025-02-07 21:17:50 浏览:655
钢管查询源码 发布:2025-02-07 21:15:25 浏览:425
滨州服务器租赁地址 发布:2025-02-07 21:13:41 浏览:438
thinkphp删除数据库数据 发布:2025-02-07 21:12:03 浏览:945
安卓智能手机哪个更便宜 发布:2025-02-07 21:10:24 浏览:144
织梦数据库连接 发布:2025-02-07 21:09:32 浏览:353
缓解情绪解压的句子 发布:2025-02-07 21:04:23 浏览:535