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

这篇具有很好参考价值的文章主要介绍了现代C++新特性 扩展的聚合类型(C++17 C++20)(PC浏览效果更佳)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

    现代C++新特性 扩展的聚合类型(C++17 C++20)(PC浏览效果更佳),c++,c++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都还没有支持该特性。文章来源地址https://www.toymoban.com/news/detail-578227.html

到了这里,关于现代C++新特性 扩展的聚合类型(C++17 C++20)(PC浏览效果更佳)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Swift的高级语法特性,如可选类型、闭包、枚举、泛型、协议、扩展等

    Swift是一门现代化的编程语言,具有许多高级语法特性,下面我们来逐一介绍。 1. 可选类型(Optional) Swift中的可选类型是一种特殊的类型,它可以表示一个值是存在或不存在的。在声明一个可选类型时,需要在类型名称后面加上一个问号(?)来表示这个类型是可选的。例如

    2024年02月04日
    浏览(35)
  • 现代C++学习指南-类型系统

    在前一篇,我们提供了一个方向性的指南,但是学什么,怎么学却没有详细展开。本篇将在前文的基础上,着重介绍下怎样学习C++的类型系统。 在进入类型系统之前,我们应该先达成一项共识——尽可能使用C++的现代语法。众所周知,出于兼容性的考虑,C++中很多语法都是合

    2024年02月08日
    浏览(39)
  • 开启C++之旅(下):引用、内联函数及现代特性(auto和范围for循环)

    上次介绍了:开启C++之旅(上):探索命名空间与函数特性(缺省参数和函数重载) 今天就接着进行c++入门的知识讲解 引用 不是新定义一个变量,而是给已存在 变量取了一个别名 ,编译器不会为引用变量开辟内存空间,它和它引用的变量 共用 同一块内存空间。通过引用,

    2024年01月17日
    浏览(78)
  • C++14特性:解锁现代C++功能以获得更具表现力和更高效的代码

    C++14是C++编程语言的一个重要里程碑,它于2014年8月发布。C++14的主要目标是构建在C++11基础上,通过提供改进和新特性来进一步完善现代C++。C++14意味着为C++开发者提供了更多的工具和功能,以便更轻松地编写高性能、安全且易于维护的代码。 C++14对C++11进行了许多有益的增强

    2023年04月14日
    浏览(32)
  • JDK8-JDK17中的新特性(var类型推断、模式匹配、Record、密封类)

    新的语法结构,勾勒出了 Java 语法进化的一个趋势,将开发者从 复杂、繁琐 的低层次抽象中逐渐解放出来,以更高层次、更优雅的抽象,既 降低代码量 ,又避免意外编程错误的出现,进而提高代码质量和开发效率。 1.1 Java的REPL工具: jShell命令 JDK9的新特性 Java 终于拥有了

    2024年02月06日
    浏览(54)
  • C++学习笔记——C++ 新标准(C++11、C++14、C++17)引入的重要特性

    目录 1、简介 2.自动类型推导和初始化 示例代码 3.智能指针 示例代码 4.Lambda 表达式 示例代码 5.右值引用和移动语义 示例代码 6.并发编程支持 示例代码 7.其他特性 八、案例:实现一个简单的并发下载器 上一篇文章:     C++标准模板库(STL)是C++的一个重要组成部分,它提

    2024年01月19日
    浏览(36)
  • 【C++】万字一文全解【继承】及其特性__[剖析底层化繁为简](20)

    前言 大家好吖,欢迎来到 YY 滴C++系列 ,热烈欢迎! 本章主要内容面向接触过C++的老铁 主要内容含: 欢迎订阅 YY 滴C++专栏!更多干货持续更新!以下是传送门! YY的《C++》专栏 YY的《C++11》专栏 YY的《Linux》专栏 YY的《数据结构》专栏 YY的《C语言基础》专栏 YY的《初学者易

    2024年02月01日
    浏览(74)
  • c++ 11 新特性 不同数据类型之间转换函数之reinterpret_cast

    一.不同数据类型之间转换函数 reinterpret_cast 介绍 reinterpret_cast 是C++中的一种类型转换操作符,用于执行低级别的位模式转换。具体来说, reinterpret_cast 可以实现以下功能: 指针和整数之间的转换 :这种转换通常用于在指针中存储额外信息,或者在特定平台上进行底层操作。

    2024年03月09日
    浏览(40)
  • C++中size_t类型详解:探究其特性与用途,附带源代码示例

    C++中size_t类型详解:探究其特性与用途,附带源代码示例 在C++编程语言中,\\\"size_t\\\"是一个常见的数据类型,用于表示内存大小或对象大小。在本文中,我们将深入了解size_t类型的特性和用途,并提供相应的源代码示例。 一、size_t类型概述 size_t是C/C++中定义的一个无符号整数类

    2024年02月06日
    浏览(39)
  • C++98,C++11、C++14 和 C++17,C++20,我应该用哪个C++标准?

    选择使用哪个C++标准取决于你的项目需求和所支持的编译器版本。 gcc编译器:使用命令行选项 -std=c++version 来指定所需的C++标准,例如 -std=c++11 、 -std=c++14 或 -std=c++17 。如果编译器不支持指定的标准,它会给出错误提示。 Microsoft Visual C++编译器,可以查看官方文档来了解每个

    2024年01月23日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包