C++20高级编程 第八章 熟悉类与对象

发布时间 2024-01-11 21:00:21作者: Mesonoxian

第八章 熟悉类与对象


声明:由于本人专门有关于OOP语义学的系列博客,因而在此处仅做简要介绍

对象的生命周期: 创建,销毁,赋值

默认构造函数

默认构造函数:没有参数的构造函数

如果没有指定任何构造函数,编译器将自动生成午餐构造函数.然而,如果声明了任何构造函数,编译器就不会再自动生成默认构造函数.

显式默认的构造函数

为了避免手动编写空的默认构造函数,C++现在支持 显式默认的默认构造函数(explicitly deleted default constructor)

export class SpreadsheetCell{
public:
    SpreadsheetCell()=default;//显式默认
    SpreadsheetCell(double initialValue);
};

显式删除的构造函数

C++还支持 显式删除的默认构造函数(explicitly deleted default constructor)

export class MyClass{
public:
    MyClass()=delete;//显式删除
};

构造函数初始化器

除了在构造函数体内初始化数据成员外,C++还提供了一种在构造函数中初始化数据成员的方法,称为 构造函数初始化器成员初始化列表,也可称为ctor-initializer.

这里举一个例子:

SpreadsheetCell::SpreadsheetCell(double initialValue)
{
    setValue(initialValue);
}

上面是在构造函数体内初始化数据成员,与下面通过构造函数初始化器初始化是等价的

SpreadsheetCell::SpreadsheetCell(double initialValue)
    :m_value(initialValue){}

某些数据成员由于其自身特性,必须在初始化器内进行初始化:

数据类型 说明
const数据成员 const变量创建后无法对其正确赋值,必须在创建时提供初始值
引用数据成员 如果不指向什么,引用将无法存在,且一旦创建引用不得改变指向目标
没有默认构造函数的
对象数据成员
C++尝试用默认构造函数初始化成员对象.如果不存在默认构造函数
,就无法初始化该对象,必须显式调用其某个构造函数.
没有默认构造函数的
基类
详见第十章

在初始化器中初始化顺序按照类内初始化顺序进行.

拷贝构造函数

C++中有一类特殊的构造函数,称为 拷贝构造函数(copy constructor).允许所创建的对象是另一个对象的副本.

export class SpreadsheetCell
{
public:
    SpreadsheetCell(const SpreadsheetCell& src);//拷贝构造
};

拷贝构造函数采用源对象的const应用作为参数,在其内部,应该复制源对象的所有数据成员.

如果没有编写拷贝构造函数,C++会自动生成一个,用源对象中相应数据成员的值初始化新对象中的每个数据成员.

C++中传递函数参数的默认方式是值传递,这意味着函数或方法接收某个值或对象的副本.因此,无论何时给函数或方法传递一个对象,编译器都会调用新对象的拷贝构造函数进行初始化.

注意:为了提高性能,最好按const引用而非按值传递对象.

显式默认或显式删除的拷贝构造函数

SpreadsheetCell(const SpreadsheetCell& src)=default;
SpreadsheetCell(const SpreadsheetCell& src)=delete;

初始化列表构造函数

初始化列表构造函数(initializer-list constructor) 将std::initializer_list作为第一个参数,且没有参数(或参数有默认值).

下面通过一个例子说明这种用法:

#include <initializer_list>
class A{
public:
    A(std::initializer_list<double>args)
    {
        for(const auto& value:args)
            this->weights.push_back(value);
    }
private:
    std::vector<double>weights;
};

委托构造函数

委托构造函数(delegating constructor) 允许狗仔函数调用同一个类的其他构造函数.然而,调用只能存储在构造函数初始化器中,且必须是列表中唯一的成员初始化器.

SpreadsheetCell::SpreadsheetCell(double value){};
SpreadsheetCell::SpreadsheetCell(std::string_view initialValue)
    :Spreadsheet(stringToDouble(initialValue)){}

在使用委托构造函数时,需要小心构造函数的递归.

转换构造函数

转换构造函数 代指某些特定单参数构造函数.编译器可以使用这些构造函数执行隐式转换.

class A{
public:
    A(int){}
};

然而,这可能并不总是想要的行为.通过将构造函数标记为 explicit,可以防止编译器进行此类隐式转换.

注意:建议将任何可以使用单参数调用的构造函数标记为explicit,以避免意外的隐式转换,除非该转换为有用的.

一般而言,转换构造函数只支持一个参数.然而,自C++11标准引入了列表初始化,转换构造函数可以有多个参数.下面是一个例子:

class A{
public:
    A(int){}
    A(int,int){}
};

A(1);//A(int(1))
A({1});//A(int(1))
A({1,2});//A(int(1),int(2))

为了避免执行此类隐式转换,两个转换构造函数都可以声明为explicit.

从C++20开始,可以将布尔参数传递给explicit,这将用于类型萃取中.

explicit(a>=15)A(int value){}
情况 编译器生成
没有定义构造函数 一个无参构造函数以及一个拷贝构造函数
只定义了默认构造函数 一个拷贝构造函数
自定义了拷贝构造函数 不会生成构造函数
只定义了一个非拷贝构造函数 一个拷贝构造函数
一个默认构造函数与
一个非拷贝构造函数
一个拷贝构造函数

销毁对象

当销毁对象时,会发生两件事:调用对象的析构函数,释放对象占用的内存.

通常而言,销毁对象会销毁在栈区的对象,但是在自由存储区中分配的对象不会自动销毁.必须对对象指针使用delete,从而调用析构函数并释放内存.

返回值优化(Return Value Optimization,RVO): 利用省略(copy elision)在返回值时优化掉成本高昂的拷贝构造函数.