现代C++新特性 扩展的聚合类型(C++17 C++20)(PC浏览效果更佳)

news2024/11/15 15:36:20

    文字版PDF文档链接:现代C++新特性(文字版)-C++文档类资源-CSDN下载 

1.聚合类型的新定义

C++17标准对聚合类型的定义做出了大幅修改,即从基类公开且非虚继承的类也可能是一个聚合。同时聚合类型还需要满足常规条件。

1.没有用户提供的构造函数。

2.没有私有和受保护的非静态数据成员。

3.没有虚函数。

在新的扩展中,如果类存在继承关系,则额外满足以下条件。

4.必须是公开的基类,不能是私有或者受保护的基类。

5.必须是非虚继承。

请注意,这里并没有讨论基类是否需要是聚合类型,也就是说基类是否是聚合类型与派生类是否为聚合类型没有关系,只要满足上述5 个条件,派生类就是聚合类型。在标准库<type_traits>中提供了一个聚合类型的甄别办法is_aggregate,它可以帮助我们判断目标类型是否为聚合类型:

#include <iostream>
#include <string>


class MyString : public string {};


int main(int argc, char** argv)
{
    cout << "is_aggregate_v<string> = " << is_aggregate_v<string> << endl;
    cout << "is_aggregate_v<MyString> = " << is_aggregate_v<MyString> << endl;

}

在上面的代码中,先通过is_aggregate_v判断string是否为聚合类型,根据我们对string的了解,它存在用户提供的构造函数,所以一定是非聚合类型。然后判断类

MyString是否为聚合类型,虽然该类继承了string,但因为它是公开继承且是非虚继承,另外,在类中不存在用户提供的构造函数、虚函数以及私有或者受保护的数据成员,所以MyString应该是聚合类型。编译运行以上代码,输出的结果也和我们判断的一致:

is_aggregate_v<string> = 0
is_aggregate_v<MyString> = 1

2.聚合类型的初始化

由于聚合类型定义的扩展,聚合对象的初始化方法也发生了变化。过去要想初始化派生类的基类,需要在派生类中提供构造函数,例如:

#include <iostream>
#include <string>


class MyStringWithIndex : public string {
public:
    MyStringWithIndex(const string& str, int idx) : string(str), index_(idx) {}
    int index_ = 0;
};


ostream& operator << (ostream& o, const MyStringWithIndex& s)
{
    o << s.index_ << ":" << s.c_str();
    return o;
}


int main(int argc, char** argv)
{
    MyStringWithIndex s("hello world", 11);
    cout << s << endl;
}

在上面的代码中,为了初始化基类我们不得不为MyStringWithIndex提供一个构造函数,用构造函数的初始化列表来初始化string。现在,由于聚合类型的扩展,这个过程得到了简化。需要做的修改只有两点,第一是删除派生类中用户提供的构造函数,第二是直接初始化:

#include <iostream>
#include <string>

class MyStringWithIndex : public string {
public:
    int index_ = 0;
};

ostream& operator << (ostream& o, const MyStringWithIndex& s)
{
    o << s.index_ << ":" << s.c_str();
    return o;
}

int main(int argc, char** argv)
{
    MyStringWithIndex s{ {"hello world"}, 11 };
    cout << s << endl;
}

删除派生类中用户提供的构造函数是为了让MyStringWithIndex成为一个C++17标准的聚合类型,而作为聚合类型直接使用大括号初始化即可。MyStringWithIndex s{{"hello world"}, 11}是典型的初始化基类聚合类型的方法。其中{"hello world"}用于基类的初始化,11用于index_的初始化。这里的规则总是假设基类是一种在所有数据成员之前声明的特殊成员。所以实际上,{"hello world"}的大括号也可以省略,直接使用MyStringWithIndex s{ "hello world", 11}也是可行的。另外,如果派生类存在多个基类,那么其初始化的顺序与继承的顺序相同:

#include <iostream>
#include <string>

class Count {
public:
    int Get()
    {
        return count_++;
    }
    int count_ = 0;
};

class MyStringWithIndex : public string, public Count {
public:
    int index_ = 0;
};

ostream& operator << (ostream& o, MyStringWithIndex& s)
{
    o << s.index_ << ":" << s.Get() << ":" << s.c_str();
    return o;
}

int main(int argc, char** argv)
{
    MyStringWithIndex s{ "hello world", 7, 11 };
    cout << s << endl;
    cout << s << endl;
}

在上面的代码中,类MyStringWithIndex先后继承了string和Count,所以在初始化时需要按照这个顺序初始化对象。{ "hello world", 7, 11}中字符串"hello world"对应基类string,7对应基类Count,11对应数据成员 index_。

​​​​​​​3.扩展聚合类型的兼容问题

虽然扩展的聚合类型给我们提供了一些方便,但同时也带来了一个兼容老代码的问题,请考虑以下代码:

#include <iostream>
#include <string>

class BaseData {
    int data_;
public:
    int Get()
    {
        return data_;
    }
protected:
    BaseData() : data_(11) {}
};

class DerivedData : public BaseData {
public:
};

int main(int argc, char** argv)
{
    DerivedData d{};
    cout << d.Get() << endl;
}

以上代码使用C++11或者C++14标准可以编译成功,而使用C++17标准编译则会出现错误,主要原因就是聚合类型的定义发生了变化。在C++17之前,类DerivedData不是一个聚合类型,所以DerivedData d{}会调用编译器提供的默认构造函数。调用DerivedData默认构造函数的同时还会调用BaseData的构造函数。

虽然这里BaseData声明的是受保护的构造函数,但是这并不妨碍派生类调用它。从C++17开始情况发生了变化,类DerivedData变成了一个聚合类型,以至于DerivedData d{}也跟着变成聚合类型的初始化,因为基类BaseData中的构造函数是受保护的关系,它不允许在聚合类型初始化中被调用,所以编译器无奈之下给出了一个编译错误。如果读者在更新开发环境到C++17标准的时候遇到了这样的问题,只需要为派生类提供一个默认构造函数即可。

​​​​​​​4.禁止聚合类型使用用户声明的构造函数

在前面我们提到没有用户提供的构造函数是聚合类型的条件之一,但是请注意,用户提供的构造函数和用户声明的构造函数是有区别的,比如:

#include <iostream>

struct X {
    X() = default;
};

struct Y {
    Y() = delete;
};

int main(int argc, char** argv)
{
    cout << boolalpha << "is_aggregate_v<X> : " << is_aggregate_v<X> << endl;
    cout << "is_aggregate_v<Y> : " << is_aggregate_v<Y> << endl;
}

用C++17标准编译运行以上代码会输出:

is_aggregate_v<X> : true
is_aggregate_v<Y> : true

由此可见,虽然类X和Y都有用户声明的构造函数,但是它们依旧是聚合类型。不过这就引出了一个问题,让我们将目光放在结构体Y 上,因为它的默认构造函数被显式地删除了,所以该类型应该无法实例化对象,例如:

Y y1; // 编译失败,使用了删除函数

但是作为聚合类型,我们却可以通过聚合初始化的方式将其实例化:

Y y2{}; // 编译成功

编译成功的这个结果显然不是类型Y的设计者想看到的,而且这个问题很容易在真实的开发过程中被忽略,从而导致意想不到的结果。除了删除默认构造函数,将其列入私有访问中也会有同样的问题,比如:

struct Y {
private:
    Y() = default;
};
Y y1;   // 编译失败,构造函数为私有访问
y y2{}; // 编译成功

请注意,这里Y() = default;中的= default不能省略,否则Y会被识别为一个非聚合类型。

为了避免以上问题的出现,在C++17标准中可以使用explicit说明符或者将= default声明到结构体外,例如

struct X {
    explicit X() = default;
};

struct Y {
    Y();
};

Y::Y() = default;

这样一来,结构体X和Y被转变为非聚合类型,也就无法使用聚合初始化了。不过即使这样,还是没有解决相同类型不同实例化方式表现不一致的尴尬问题,所以在C++20标准中禁止聚合类型使用用户声明的构造函数,这种处理方式让所有的情况保持一致,是最为简单明确的方法。同样是本节中的第一段代码示例,用C++20环境编译的输出结果如下

is_aggregate_v<X> : false
is_aggregate_v<Y> : false

值得注意的是,这个规则的修改会改变一些旧代码的意义,比如我们经常用到的禁止复制构造的方法:

struct X {
    string s;
    vector<int> v;
    X() = default;
    X(const X&) = delete;
    X(X&&) = default;
};

上面这段代码中结构体X在C++17标准中是聚合类型,所以可以使用聚合类型初始化对象。但是升级编译环境到C++20标准会使X转变为非聚合对象,从而造成无法通过编译的问题。一个可行的解决方案是,不要直接使用= delete;来删除复制构造函数,而是通过加入或者继承一个不可复制构造的类型来实现类型的不可复制,例如

struct X {
    string s;
    vector<int> v;
    [[no_unique_address]] NonCopyable nc;
};

// 或者
struct X : NonCopyable {
    string s;
    vector<int> v;
};

这种做法能让代码看起来更加简洁,所以我们往往会被推荐这样做。

​​​​​​​5.使用带小括号的列表初始化聚合类型对象

通过2中,我们知道对于一个聚合类型可以使用带大括号的列表对其进行初始化,例如

struct X {
    int i;
    float f;
};

X x{ 11, 7.0f };

如果将上面初始化代码中的大括号修改为小括号,C++17标准的编译器会给出无法匹配到对应构造函数X::X(int, float)的错误,这说明小括号会尝试调用其构造函数。这一点在C++20标准中做出了修改,它规定对于聚合类型对象的初始化可以用小括号列表来完成,其最终结果与大括号列表相同。所以以上代码可以修改为

X x(11, 7.0f);

另外,前面的章节曾提到过带大括号的列表初始化是不支持缩窄转换的,但是带小括号的列表初始化却是支持缩窄转换的,比如

struct X {
    int i;
    short f;
};

X x1{ 11, 7.0 }; // 编译失败,7.0从double转换到short是缩窄转换
X x2(11, 7.0);   // 编译成功

需要注意的是,到目前为止该特性只在GCC中得到支持,而CLang 和MSVC都还没有支持该特性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/735230.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用C语言写一个压缩文件的程序

本篇目录 数据在计算机中的表现形式huffman 编码将文件的二进制每4位划分&#xff0c;统计其值在文件中出现的次数构建二叉树搜索二叉树的叶子节点运行并输出新的编码文件写入部分写入文件首部写入数据部分压缩运行调试解压缩部分解压缩测试为可执行文件配置环境变量总结完整代…

23数字图像置乱技术(matlab程序)

1.简述 一、引言 所谓“置乱”&#xff0c;就是将图像的信息次序打乱&#xff0c;a像素移动到b像素位置上&#xff0c;b像素移动到c像素位置上&#xff0c;……&#xff0c;使其变换成杂乱无章难以辨认的图片。数字图像置乱技术属于加密技术&#xff0c;是指发送发借助数学或者…

Python实现PSO粒子群优化算法优化Catboost分类模型(CatBoostClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 PSO是粒子群优化算法&#xff08;Particle Swarm Optimization&#xff09;的英文缩写&#xff0c;是一…

《低代码指南》——轻流5.0发布,无代码引擎矩阵全面升级

7月6日,由轻流主办「无代码无边界 202376Day|轻流无代码探索者大会」于上海顺利举行。轻流也在会上重磅发布了更加开放、灵活、低门槛的轻流5.0,和全面升级的专有轻流。 轻流5.0全面迭代升级了轻流的无代码引擎矩阵(表单引擎、流程引擎、报表引擎、门户引擎、数据引擎)。…

软件测试项目实战,电商项目测试实例 - 业务测试(重点)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 支付功能怎么测试…

pytest自动化测试实战之执行参数

上一篇介绍了如何运行pytest代码&#xff0c;以及用例的一些执行规则&#xff0c;执行用例发现我们中间print输出的内容&#xff0c;结果没有给我们展示出来&#xff0c;那是因为pytest执行时&#xff0c;后面需要带上一些参数。 参数内容 我们可以在cmd中通过输入 pytest -h…

域名捡漏的好方法,希望能够帮到你:域霸扫描器 V0.44 绿色免费版,供大家学习研究参考

高速扫描域名的工具&#xff0c;一均程序每小时五万条。 扫描域名是否注册&#xff0c;注册商是谁&#xff0c;域名的注册日期与过期日期。 供大家学习研究参考&#xff01; 下载&#xff1a;https://download.csdn.net/download/weixin_43097956/88025564

【SpringBoot——Error记录】

IDEA正常安装后&#xff0c;运行按钮为灰色解决方法尝试 解决方法一&#xff08;本人适用&#xff09;解决方法二 解决方法一&#xff08;本人适用&#xff09; 检查创建项目时JDK是否添加&#xff0c;版本是否正确。 解决方法二 点击左下角的Structure 参考链接&#xff1…

回归预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计学习总结参考资料 预测效果 基本介绍 回归预测 …

node中的数据持久化之mongoDB

一、什么是mongoDB MongoDB是一种开源的非关系型数据库&#xff0c;正如它的名字所表示的&#xff0c;MongoDB支持的数据结构非常松散&#xff0c;是一种以bson格式&#xff08;一种json的存储形式&#xff09;的文档存储方式为主&#xff0c;支持的数据结构类型更加丰富的NoS…

mysql多表查询练习题

创建表及插入数据 create table if not exists dept3( deptno varchar(20) primary key , -- 部门号 name varchar(20) -- 部门名字 ); -- 创建员工表 create table if not exists emp3( eid varchar(20) primary key , -- 员工编号 ename varchar(20), -- 员工名字 age int, -…

换零钱——最小钱币张数(贪心算法)

贪心算法&#xff1a;根据给定钱币面值列表&#xff0c;输出给定钱币金额的最小张数。 (本笔记适合学完python基本数据结构&#xff0c;初通 Python 的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣…

CS EXE上线主机+文件下载上传键盘记录

前言 书接上文&#xff0c;CobaltStrike_1_部署教程及CS制作office宏文档钓鱼教程&#xff0c;该篇介绍【使用CS生成对应exe木马&#xff0c;上线主机&#xff1b;对上线主机进行&#xff0c;文件下载&#xff0c;文件上传&#xff0c;键盘记录】。 PS&#xff1a;文章仅供学习…

unseping

代码审计 <?php highlight_file(__FILE__);class ease{private $method;private $args;function __construct($method, $args) {$this->method $method;$this->args $args;}function __destruct(){if (in_array($this->method, array("ping"))) {call…

关于 colab Tutorial的介绍

&#xff08;一&#xff09;常用的快捷键 (二) 网上环境的配置 按照官网上所给的提示一步一步操作即可 注意&#xff1a;此平台需要科学的上网

word因导入mathtype不能使用复制粘贴快捷键的解决方法

1. 我们安装完mathtype后&#xff0c;有时会有两个mathtype显示&#xff0c;其中一个是属于office文件夹下的&#xff0c;另一个是win文件夹下的。如图&#xff1a; 2. 如果word中的复制粘贴快捷键&#xff08;CTRLC和CTRLV&#xff09;不能用&#xff0c;通常是因为office路径…

Arduino STM32F103C8+ST7735 1.8‘‘3D矢量图形demo

Arduino STM32F103C8ST7735 1.8’3D矢量图形demo &#x1f4cc;开源项目地址&#xff1a;https://github.com/cbm80amiga/ST7735_3d_filled_vector&#x1f527;所需库&#xff1a;https://github.com/cbm80amiga/Arduino_ST7735_STM&#x1f516;本开源工程基于Arduino开发平台…

JavaWeb JSP基础语法和指令

1. JSP语法 JSP是Java技术的一种应用&#xff0c;对Java所有的语法都支持&#xff0c;除此之外&#xff0c;还有一些扩充的语法。 1&#xff09;输出变量 <% new java.util.Date()%> <% name %> 2) 执行java代码 <% int a 0, b 1, t; for(int i0;i<10;i)…

day61_SSM+自定义注解实现日志记录

SSM自定义注解AOP实现日志记录 1 需求 工作中,经常会遇到记录日志的动作,以前是使用日志框架来实现,现在可以使用注解来实现,使用起来更方便,随用随加~ 今天我们演示在SSM的基础上,对普通的方法加上自定义注解,注解中写上该方法的日志信息,然后将日志信息记录到数据库中. 编…

时间序列预测 | Matlab移动平均模型MA时间序列预测

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列预测 | Matlab移动平均模型MA时间序列预测 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量 warning off % 关闭报警信息…