C++对象模型:第2章(1)-构造函数

2024-08-28 04:08
文章标签 c++ 模型 对象 构造函数

本文主要是介绍C++对象模型:第2章(1)-构造函数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、默认构造函数

以下两个观点是错误的:

(1)任何类,如果没有定义默认构造函数,编译器就会自动合成一个(但是作者Stanley B. Lippman在Primer中说“任何类没有显式定义构造函数就会默认生成一个”,这两种说法有点冲突)。

(2)编译器合成的默认构造函数会显式将类的每个成员设定为默认值。


        有四种情况,会造成编译器必须为未声明构造函数的类合成一个默认构造函数。C++标准将这些合成的默认构造函数称作“隐式有用构造函数(implicit nontrivial default constructors)”。被合成的构造函数只能满足编译器(而不是程序)的需求;它之所以能完成任务是因为以下四种情况。

       如果类不是以下四种情况又没有声明任何构造函数,那么它们拥有的是implicit trivial default constructor,它们实际上并没有被合成出来。


 C++ standard:对于class X ,如果没有任何user-declared constructor,那么会有一个default constructor 被暗中(implicitly)声明出来,一个被暗中声明出来的default constructor将是一个trivial(浅薄而无能,没啥用的)constructor


        默认情况下,全局变量的内存会保证在程序启动时被初始化为0,但是局部变量位于程序的堆栈中,堆对象位于自由空间中,都不会被初始化为0,它们的内容是内存上次被使用时的“遗迹”

#include <iostream>
#include <string>
using namespace std;class A {
public:int val; 
};int main() {A a1;  // A a1 = A();cout << a1.val << endl;
}
上面的程序在VS2010下能够通过编译但是在执行时出错:提示“The variable ‘a1' is being used without being initialized”;但是在GCC下会输出负的随机值。

如果将main函数中第一个语句改为注释的那样:创建一个空的A的对象,然后将其赋值给a1,则会有如下结果:在VS2010下和在GCC均输出0.


要是将class A中再增加一个string成员,如下面代码:

#include <iostream>
#include <string>
using namespace std;class A {
public:int val; string str;
};int main() {A a1;  // A a1 = A();cout << a1.val << endl;
}
此时在VS2010下会输出随机值(而不是如第一段程序那样运行时错误);但是在GCC下会输出正的随机值。

如果将main函数中第一个语句改为注释的那样:创建一个空的A的对象,然后将其赋值给a1,则会有如下结果:VS2010会输出随机值,而GCC会输出0.



这两个程序的运行结果似乎表明,默认构造函数的行为很复杂,和编译器相关,具体看下面:

情况1:一个类的某成员带有默认构造函数时

         如果一个类没有任何构造函数,但是它的一个成员对象有默认构造函数,例如上面第二段程序:A类没有构造函数,但是它的成员string类型的str有默认构造函数,那么这个类的implicit default constructor就是“nontrivial”(有用的)的,编译器需要为该类合成一个默认构造函数。不过这个合成操作指引在构造函数真正需要调用时才会发生。被合成的默认构造函数包含必要的代码,能够调用A的成员str的默认构造函数,但是它并不产生任何代码来初始化A::val【因此本文开篇第二种说法错误】。是的,初始化str是编译器的责任,但初始化val则是程序员的责任

那么,程序员可以定义一个显式的构造函数来初始化val,如:

<pre name="code" class="cpp"><pre name="code" class="cpp">class A {
public:A() {  val = 0; }int val; string str;
};

 
 

         这样的话,程序的需求满足了,但是编译器还需要初始化类成员str。由于默认构造函数被显式定义了,那么编译器没办法合成第二个,此时编译器的行动是:“如果类A内含有一个或一个以上的类成员对象,那么类A的每个构造函数必须调用每个类成员的默认构造函数”。编译器会扩张已经存在的构造函数,在其中插入一些代码,使得用户代码执行前,必须先调用必要的默认构造函数,那么扩张后的构造函数类似这样:

<pre name="code" class="cpp">class A {
public:A() {  val = 0; str = string(); }int val; string str;
};

 

情况2:带有默认构造函数的基类

         和情况1类似:如果一个没有任何constructor的class派生自一个带有default constructor的base class,那么这个derived class的default constructor会被视为nontrivial,并因此需要被合成出来。

        如果设计者提供了多个构造函数,但其中没有默认构造函数,编译器同样会扩张现有的每个构造函数,将“用以调用所有必要之构造函数”的程序代码加进去。它不会合成新的默认构造函数,因为存在设计者显式定义的构造函数。

情况3:带有虚函数的类

        当一个类声明了或继承的类里有虚函数时,同样要合成默认构造函数。

当没有显式声明一个构造函数时,编译器会进行两个扩张行为:

(1)编译器会产生一个虚函数表(vtbl,virtual function table),表内存放虚函数地址;

(2)在每一个类对象中,编译器会额外合成一个指针成员(vptr,pointer member),该指针成员指向虚函数表的地址

        编译器必须为每个这种类的对象的vptr设定初值,放置适当的虚函数表的地址。对于类所定义的每个构造函数,编译器会安插一些代码来做这样的事情。对于那些未声明任何构造函数的类,编译器会为它们合成一个默认的,以便正确地初始化每个类对象的vptr。

情况4:带有虚基类的类

多重继承和虚继承的内存布局

如下的代码,Top类是一个虚基类:

#include <iostream>
#include <string>
using namespace std;class Top {
public:Top():a(1) {}int a;
};class Left: virtual public Top {
public:Left(): b(2) {}int b;
};class Right: virtual public Top {
public:Right():c(3){}int c;
};class Buttom: public Left, public Right {
public:Buttom(): d(4){}int d;
};void foo(const Right *pa) {pa->a = 1024;
}int main() {//这里输出类的大小,分别为4,12,12,24cout << sizeof(Top) << " " << sizeof(Left) << " " << sizeof(Right) << " " <<sizeof(Buttom) << endl;foo(new Right);foo(new Right);
}
在编译的时候函数foo无法确定pa->Top::a的实际位置,因为pa的真正类型是可变的。编译器只有在执行的时候才决定其位置。foo函数可能会被编译器写成这样:

void foo(const  Right *pa) { pa->__vbcX->a = 1024; }

__vbcX是编译器所产生的指针,指向虚基类Top,该指针时再类对象构造期间完成的。对于类定义的每个构造好桉树,编译器都会安插那些“允许每一个虚基类的执行期存取操作”的代码。如果类没有声明任何构造函数,编译器必须为它合成一个默认的。

这篇关于C++对象模型:第2章(1)-构造函数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1113680

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}