#include<iostream>
using namespace std;
class A
{
public:
int i;
};
class B: virtual public A
{
public:
int j;
};
class C: virtual public A
{
public:
int k;
};
class D: virtual public B, virtual public C
{
public:
int l;
};
int main()
{
cout<<" A : "<<sizeof(A)<<endl;
cout<<" B : "<<sizeof(B)<<endl;
cout<<" C : "<<sizeof(C)<<endl;
cout<<" D : "<<sizeof(D)<<endl;
}
输出:
1
8
8
16
根据我的理解,在虚拟继承期间,每个基类都有一个vptr,因此类D有两个vptr,大小为16。
假设D类也有虚拟函数假设虚拟~D()。现在大小应该增加到24(D的一个vptr)。
但事实并非如此。
我有些误解虚拟ptr概念。
任何人都可以解释清楚吗?
答案 0 :(得分:1)
D
需要一个vptr,无论它是否具有任何虚函数,都与B
和C
一样多。
原因是编译器需要一种方法从D
的任何实例到其A
,B
或C
基类子对象。由于该继承是虚拟的,因此某些类E
可能来自D
,但也来自具有相同虚拟基础的其他事物。 D
的布局必须考虑到E
存在的可能性。
因此,从作为D
实例的完整对象到其基类子对象,没有固定的偏移量,因为这个完整对象可能是as-yet-的一个实例。未定义E
。
我不确定这是否完全说明了你看到的行为。我从未使用sizeof(int) == 1
的实现,而且我没有任何理由来解释为什么B
和D
为8和16。
答案 1 :(得分:0)
为什么要向D
添加虚拟功能需要额外的功能
vptr
?课程图片将以B
或C
开头,并且
D
可以只将其函数附加到其中。
(只要你有虚拟继承,你需要一些
一个指针,即使没有虚函数。)
向A
添加虚拟功能将有所作为
(几乎可以肯定,当然,这里的一切都是
实施依赖)。对于它的价值,您可能想要
尝试类似以下内容:
#include <iostream>
#include <iomanip>
#include <cstdint>
typedef std::uintptr_t Word;
class SaveIOFormat
{
std::basic_ios<char>* myStream;
char myFill;
std::basic_ios<char>::fmtflags myFlags;
int myPrecision;
public:
SaveIOFormat( std::basic_ios<char>& stream )
: myStream( &stream )
, myFill( stream.fill() )
, myFlags( stream.flags() )
, myPrecision( stream.precision() )
{
}
~SaveIOFormat()
{
myStream->fill( myFill );
myStream->flags( myFlags );
myStream->precision( myPrecision );
}
};
template <typename T>
class DumpAsWords
{
Word const* myObj;
typedef Word const* Iterator;
typedef char sizeMustBeMultipleOfSizeofWord
[ sizeof(T) % sizeof(uintptr_t) == 0 ? 1 : -1 ];
static int const ourLength = sizeof(T) / sizeof(Word);
public:
DumpAsWords( T const& obj )
: myObj( reinterpret_cast<Word const*>( &obj ) )
{
}
friend std::ostream& operator<<( std::ostream& dest,
DumpAsWords const& obj )
{
SaveIOFormat saveExcursion( dest );
dest.fill( '0' );
dest.setf( std::ios::hex, std::ios::basefield );
for ( Iterator current = obj.myObj, end = obj.myObj + ourLength;
current != end;
++ current ) {
if ( current != obj.myObj ) {
dest << ' ';
}
dest << std::setw( sizeof(Word) * 2 ) << *current;
}
return dest;
}
};
template <typename T>
DumpAsWords<T>
dump( T const& obj )
{
return DumpAsWords<T>( obj );
}
class B
{
Word i;
public:
B() : i( 1 ) {}
virtual ~B() {}
};
class L : virtual public B
{
Word i;
public:
L() : i( 2 ) {}
};
class R : virtual public B
{
Word i;
public:
R() : i( 3 ) {}
};
class D : public L, public R
{
Word i;
public:
D() : i( 4 ) {}
};
int
main()
{
D aD;
std::cout << "sizeof B: " << sizeof(B) << std::endl;
std::cout << "sizeof L: " << sizeof(L) << std::endl;
std::cout << "sizeof R: " << sizeof(R) << std::endl;
std::cout << "sizeof D: " << sizeof(D) << std::endl;
std::cout << std::endl;
std::cout << "addrof B: " << static_cast<B*>( &aD ) << std::endl;
std::cout << "addrof L: " << static_cast<L*>( &aD ) << std::endl;
std::cout << "addrof R: " << static_cast<R*>( &aD ) << std::endl;
std::cout << "addrof D: " << static_cast<D*>( &aD ) << std::endl;
std::cout << std::endl;
std::cout << "dump: " << dump( aD ) << std::endl;
return 0;
}
(如果它有点长,那是因为我复制/粘贴了一些代码 从我的图书馆,使其更简单,更清洁。)
在我的机器上,这给出了:
sizeof B: 16
sizeof L: 32
sizeof R: 32
sizeof D: 56
addrof B: 00000000001AFEB8
addrof L: 00000000001AFE90
addrof R: 00000000001AFEA0
addrof D: 00000000001AFE90
dump: 000000013fb90bb0 0000000000000002 000000013fb90bb8 0000000000000003 0000000000000004 000000013fb90ba8 0000000000000001
正如你所看到的,顺序是D中的L,D中的R,D,B(L,R和 d)。 D与L共享vptr。
(这是在64位Windows机器上用VC ++ 11编译的。你的结果很容易就会有所不同。)