深入探索c++对象模型(三、构造函数语义<下>)

本来打算这一篇不拆分了,不过想想还是拆开吧,这样更容易看一定,一篇文章太长,不好看。这一篇就接着上一篇没有分析完的继续分析。

3.1 "带有一个virtual Function"的Class

这个看着英语就懂了,比较简单。

就是一个类中如果存在一个虚函数,或者继承父类有虚函数,编译器都会合成一个默认的构造函数。

合成这个构造函数的时候,编译器又插入了什么代码?下面我们来一探究竟。

3.1.1 没有构造函数

还是原来的操作,上代码:

#include <iostream>
#include "stdio.h"

class A
{
public:
    virtual int fun1(int a);
};

int A::fun1(int a)
{
    return a;
}


int main()
{
    A a;


    return 0;
}

我们来反汇编:

main:
.LFB1022:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	leaq	-16(%rbp), %rax
	movq	%rax, %rdi
	call	_ZN1AC1Ev		// 类A的构造函数
	movl	$0, %eax
	movq	-8(%rbp), %rdx
	xorq	%fs:40, %rdx
	je	.L6
	call	__stack_chk_fail

编译器会默认合成类A的构造函数,接下来我们看看构造函数里做了啥:

_ZN1AC2Ev:
.LFB1024:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -8(%rbp)
	movl	$_ZTV1A+16, %edx  // 这个是重点
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

_ZTV1A是类A的虚函数表,+16是在虚函数表的开始偏移16个字节,就是把虚函数表+16个字节,赋值给类中的vptr指针。

(gdb) p a
$18 = {_vptr.A = 0x4008e8 <vtable for A+16>}

可以用gdb来查看这个类的变量。

我们可以简单看一下虚函数表的结构:

_ZTV1A:
	.quad	0				# 虚函数表开始,为0
	.quad	_ZTI1A			# type info 记录对象指针的真是类型,用于运行时多态
	.quad	_ZN1A4fun1Ei	# 就是我们的虚函数地址
	.weak	_ZTI1A
	.section	.rodata._ZTI1A,"aG",@progbits,_ZTI1A,comdat
	.align 8
	.type	_ZTI1A, @object
	.size	_ZTI1A, 16

这里就简单看一下,以后再详细分析虚函数表。

3.1.2 有构造函数的时候

如果我们写了构造函数呢?按照之前的经验,编译器会在我们写的构造函数中,插入必要的代码,如果是有虚函数,插入的就是初始化vptr指针等代码。

我们就用代码来看看:

class A
{
public:
    A()			// 插入部分
    {
        
    }

    virtual int fun1(int a);
};

反汇编查看:

_ZN1AC2Ev:
.LFB1022:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movq	%rdi, -8(%rbp)
	movl	$_ZTV1A+16, %edx
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax)
	nop
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

跟之前的反汇编代码差不多。

3.2 "带有一个virtual Base class"的class

还有最后一种情况,就是带有虚基类,出现这个虚基类的情况,是在菱形继承中出现的。

3.2.1 没有构造函数

我们就来上代码试试:

// 带有虚基类

#include <iostream>

using namespace std;

// 整体是菱形继承
class X
{

};

class A : virtual public X
{

};

class B : virtual public X
{

};

class C : public A, public B
{

};



int main(int argc, char **argv)
{
    C cc;

    return 0;
}

继承关系:

在这里插入图片描述

反汇编的结果:

main:
.LFB1021:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$48, %rsp
	movl	%edi, -36(%rbp)
	movq	%rsi, -48(%rbp)
	movq	%fs:40, %rax
	movq	%rax, -8(%rbp)
	xorl	%eax, %eax
	leaq	-32(%rbp), %rax
	movq	%rax, %rdi
	call	_ZN1CC1Ev		// 生成了类C的构造函数
	movl	$0, %eax
	movq	-8(%rbp), %rdx
	xorq	%fs:40, %rdx
	je	.L7
	call	__stack_chk_fail

查看反汇编代码,就知道这个有生成类C的构造函数,类C构造函数中的代码,可以简单认识一下:

_ZN1CC1Ev:
.LFB1032:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	subq	$16, %rsp
	movq	%rdi, -8(%rbp)
	movq	-8(%rbp), %rax
	movq	%rax, %rdi
	call	_ZN1XC2Ev		// 类X构造函数
	movl	$_ZTT1C+8, %edx
	movq	-8(%rbp), %rax
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZN1AC2Ev		// 类A构造函数
	movl	$_ZTT1C+16, %edx
	movq	-8(%rbp), %rax
	addq	$8, %rax
	movq	%rdx, %rsi
	movq	%rax, %rdi
	call	_ZN1BC2Ev		// 类B的构造函数
	movl	$_ZTV1C+24, %edx
	movq	-8(%rbp), %rax
	movq	%rdx, (%rax)
	movl	$_ZTV1C+48, %edx
	movq	-8(%rbp), %rax
	movq	%rdx, 8(%rax)
	nop
	leave
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc

关于虚基类的详细分析,我们后面才分析。

3.2.2 有构造函数

当有构造函数的时候,编译器会自动给我们插入编译器需要的代码,这个我们已经很熟悉了。

class C : public A, public B
{
public:
    C(){}		// 增加的部分
};

返汇编结果就跟上面的一样,就不写了吧,大家可以自己去试试。

3.3 总结

侯捷老师都有总结,我也来总结一波。

我们这一节和上一节总结列出了四种情况,这四种情况都会导致编译器必须为未声明构造函数的类合成一个缺省的构造函数。

现在是否理解了编译器需要和程序员需要了?

编译器合成默认构造函数都是编译器需要,并且合成的默认构造函数只满足编译器,编译器不知道程序员需要干啥,编译器只知道编译器需要什么。

从四种情况中发现,编译器合成的构造函数,一般都是为了调用父类的构造函数或者是类中的对象的构造函数,如果存在虚函数,则准备虚函数表和指向虚函数表的指针,如果存在虚基类,也是为了调用各个父类的构造函数和生产虚虚函数表等。

没看到这个文章之前,我们也有两个常见的误解:

  1. 任何class如果没有定义构造函数,就会被合成出一个来。
  2. 编译器合成出来的默认构造函数会明确设定类内每一个成员变量的初始值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值