构造/析构函数那些事儿

文章介绍了构造和析构函数的基本概念,通过示例展示了C++中编译器如何处理默认构造函数和隐式类型转换。作者强调了构造函数在赋予对象意义和析构函数销毁对象数据的角色,并讨论了何时应提供默认构造函数,包括成员类、父类以及虚函数和虚基类的情况。最后,提出了构造和析构函数的设计原则,包括避免默认构造函数、处理单参数构造函数和确保对象完全初始化等。
摘要由CSDN通过智能技术生成

目录

初识构造/析构函数

一些“题外话”

再识构造/析构函数

如何设计构造/析构函数


初识构造/析构函数

        OK,我相信大部分小伙伴认识构造函数是源于课堂,取自老师。

        让我们一同回忆课堂,面对对象编程(object-oriented programing)的老师都是如何讲述构造/析构函数。

        无论你就读于哪里,我想老师大致都讲过这些:

        1.构造函数在类被创建时被调用。

        2.如果你从没写过一个构造函数,编译器会送你一个 “默认构造函数”。

        3.析构函数在类被销毁时调用。

        4.同样的,如果你没有定义过析构函数,编译器会送你一个。

        5.它们没返回值类型

        好了,我想大部分老师都会讲述这些了。但是...如果构造/析构函数如此粗浅的话,要他有何用?本杰明是傻了吗?还有它构造/析构函数什么档次?胆敢与众函数不同?

        我不知道大家有没有这样的问题,反正我的心中有太多太多疑惑了。

        时间解答了我的问题,让我来聊聊一些“题外话”放松以下。

一些“题外话”

        我曾有幸见到了以下这样有趣的代码,这是你在java中所不能看见的。

class Fraction {    

  public:

        Fraction(int numerator, int denominator){

                       this->numerator = numerator;

                       this->denominator = denominator;

}

        operator double() { return static_cast<double>(numerator) / denominator; }

private:

        int numerator;

        int denominator;

}; 

OK,以上是我们对“分数类”的简单定义。对,这很简单;但是,如果配合上以下的代码,阁下又如何理解、释义呢?

int main() {     

        Fraction fra_num = Fraction(1, 2);     

        std::cout << fra_num;

        return 0;

}

当初我看到这段代码的时候,我的反应:“WTF,are you kidding me?”

这是妥妥的不能通过编译啊,为什么呢?你没对<<重载啊!这能用吗?

现在回头看看自己可真傻!哦,这段代码是可以通过编译的!!!

编译会穷尽一切手段使你的代码合法话!!!因为它的职责是让程序跑起来,它可不管是怎么跑的!!!因为考虑代码如何运行是你的职责!

对对对!我们是没有重载 操作符<< 但是,我们重载过double() 也就是转换函数。

于是你可爱的编译器是这样理解的:

        哦,亲爱的合作伙伴。我猜你一定是要把fra_num转变成double类型,然后输出。一定!

so!你在屏幕上获得了 0.5;

我们再把代码改一改,倘若是这样的,阁下如何理解、释义?

class Fraction {    

  public:

        Fraction(int numerator, int denominator = 1){

                       this->numerator = numerator;

                       this->denominator = denominator;

}

        operator double() { return static_cast<double>(numerator) / denominator; }

private:

        int numerator;

        int denominator;

}; 

int main() {     

        Fraction fra_num = 1;     

        std::cout << fra_num;

        return 0;

}

我想大部分同学会 “c语言”连连, fra_num = 1;是个什么玩意儿?

哦~不妨我们这样看,

我们定义了一个构造函数 其中denominator 的默认值为1,如果你什么都不给的话,denominator = 1;

记住!编译会不择手段的让你的代码合法!

编译器开始发现了你的“意图”:

        老兄,你真会写代码,你一定是想写 Fraction fra_num = Fraction(1);吧!哦好像少一个参数,哦~你还放了个默认值啊,这回我可算懂了;

        你是想写 Fraction fra_num = Fraction(1, 1);

于是...你的代码通过编译,并在屏上获得了 1。

这是些让人哭笑不得的代码释义!也许你是想这样做,但也有可能这完全不在你的意料之中!

再识构造/析构函数

抱歉,或许我有点“跑题”了。接下来,让我们闲言少叙,书归正传。

我不知道大家是否从那些 “题外话” 中看到了些什么东西。

这些令哭笑不得的代码另一番滋味,别有一番天地!

没错!你应该发现了,它们:

1.隐式转换函数 也没有 返回值

2.单参数的构造函数起到了类型转换的作用

本杰明对构造函数是这样解释得:构造函数的价值在于让对象实例化,赋予数据意义;

你想起了一个问题吗?编译器会送一个“默认构造函数”

没错,编译器的职责在于让对象实例化,赋予数据意义;而你的责任是初始化,决定数据拥有怎样的含义。

相同的,你应该学会揣摩析构函数诞生的缘由了。

析构函数毁灭对象的数据,让其不具意义;注意是毁灭数据,而不是释放内存归还地址

所以你可能会在一些vector编码看到这样的段落与设计

template<class T>
void my_vector<T>::pop_back() {
    --m_size;
    m_data[m_size].~T();
    return;
}

这里就摧毁了最后一个结点的对象数据,是对象数据,如果是基本类型可没什么作用;

好吧,我们从语法层面上,更深一步的理解了构造/析构函数了。

但这仅仅是语法层面,如果知道这,那我们可对构造函数理解可还不够深刻。

接下来,我将从语义层面尽可能消除你对构造函数的误解。

误解1:认为任何时候编译器都会提供构造函数;

        哦~,我知道,我知道。你们一定会说“这可没什么误解!这道我会解答,当我们给出构造函数时,编译器便不会给出默认构造函数”。

        嗯,你们说得对,但是我所说的并非如此,它还包括扩展;或者说,我们在大多数设计时,并不会给出默认构造函数,因为我们需要避免无意义的对象出现,或者说可以让我偷个小懒。但是,现实很骨感。在特定情况下,缺失默认构造函数那是令人痛苦的

(1)应当给“类成员”设计默认构造函数

           当你的程序中,出现了一个类内含另一个类。我们称被内含的类为“成员类”

            如果,你的成员类没有设计 “默认构造函数”,那是糟糕的。因为他会导致,内含类也无法构造。

 例如上图,A并没有“默认构造函数”,但是当我们想要构建B类对象时,我们一定要调用A类的构造函数。因为构造函数的语义是让对象实例化,赋予数据意义,所以编译器一定会想办法在B类创建时就创建完成A类。没错!我们并没有任何数据传入,或者说就算设计B(int , int),B(A a, int b)也是无用的。因为在进入构造函数B()的那一刻,A a就应该被创建完成。

你可以想想,构造函数内的语句几乎就是赋值!怎么样才能赋值?那得先有让我存放东西的地方啊!连存放物品的位置都不知道,谈何存放?

没错,此时因为类A中没定义默认构造函数 从而导致类B也无法构造,原因不在于B,而在于A;

如果我们在进入B时就应该创建好类A,那么问题就好理解了。此时我们是没有办法传入参数供编译器创建A类对象的,这也就意味着A类的构造函数必须有一个空参数列表的构造函数!而这就是默认构造函数!

同样的我们能够理解

(2)应当给父类设计默认构造函数

除此以外,

(3)应该给带有虚函数的类创建默认构造函数

(4)应该给虚继承的父类创建默认构造函数

这两种情况只需要简单了解C++的类设有虚函数表、虚基类表。当然我必须承认这得看编译器。

如果是Microsoft C++那虚基类表是独立的,但如果是cfront那虚基类表是含于虚函数表内的。

但无论如何在创建时,如若它们中存有数据,那么在创建类时需要按照具体情况放置指针类

如何设计构造/析构函数

构造/析构函数并非天生安全。然而,一旦错误来自它们,那将是痛苦的寻找过程。

总得来说,我的设计应当遵循以及秉持以下态度和理念

(1)万不得已不提供默认构造函数

(2)谨防单参数构造函数带来的影响(可避免 如使用关键字 explicit)

(3)严防构造函数创对象不完全的情况出现(需要初始化成员列表+初始化函数 、 用类封装)

(4)严禁析构函数流出异常(抓取所有异常)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值