C++17 新特性_第二章 C++17 语言特性_std::any和string_view

本文记录C++17新特性之std::any和string_view.

文章目录

  • [第2章 C++17标准库特性](#第2章 C++17标准库特性)
    • [2.3 std::any](#2.3 std::any)
      • [2.3.1 常用方法](#2.3.1 常用方法)
      • [2.3.2 使用举例](#2.3.2 使用举例)
      • [2.3.3 any 和 variant 对比](#2.3.3 any 和 variant 对比)
    • [2.4 std::string_view](#2.4 std::string_view)
      • [2.4.1 实现原理](#2.4.1 实现原理)
      • [2.4.2 使用示例](#2.4.2 使用示例)

第2章 C++17标准库特性

2.3 std::any

在C++11和C++14中,如果我们想在同一个变量中存储不同类型的值,可以使用如下方法:

方式1,void* 指针:这是 C 语言时代的解决方案。虽然灵活,但它完全丧失了类型信息,极其不安全,且无法自动管理内存(需要手动转换和释放)。

方式2,union:只能存储预定义的几种类型,且对于非平凡类型(non-trivial types,如 std::string)的支持非常有限且复杂。

C++17中 std::any 的引入旨在提供一种类型安全、拥有值语义且无需模板参数即可持有任意类型对象的标准解决方案。

2.3.1 常用方法

1 has_value() : 返回一个bool值,用于检查std::any对象当前是否包含一个值。

2 type():返回一个const std::type_info & ,代表当所存储对象的类型。结合typeid()判断要处理的类型。

3 std::any_cast(): 有两种使用方式

值版本: std::any_cast(a)。如果类型 T 与 a 中存储的类型匹配,它会返回该值的拷贝或引用。如果类型不匹配,它会抛出 std::bad_any_cast 异常。

指针版本: std::any_cast(&a)。这个版本更安全,因为它不会抛出异常。如果类型匹配,它返回一个指向所存储对象的指针;如果不匹配,则返回 nullptr。

4 reset(): 销毁 std::any 中存储的对象,使其变为空。

2.3.2 使用举例

使用举例:测试上面的any的成员方法。

cpp 复制代码
    void test()
    {
		std::any a = 42; // 存储一个整数
		cout << "存储的整数: " << std::any_cast<int>(a) << endl;
        // 存储的整数: 42
        std::cout << "Has value? " << a.has_value() << std::endl;
        // Has value? 1

        // 存储string类型
        a = std::string("hello world");
        if (a.type() == typeid (std::string))
        {
			std::cout << "存储的字符串: " << std::any_cast<std::string>(a) << std::endl;
        }
        else if (a.type() == typeid(int))
        {
			std::cout << "存储的整数: " << std::any_cast<int>(a) << std::endl;
        }
        // 存储的字符串: hello world

        // 重置 reset
		a.reset();

        // 测试转换 
        std::any a2 = 3.14; 
        try 
        {
            // 正确转换
            double val = std::any_cast<double>(a2);
            std::cout << "Value: " << val << std::endl;
            // Value: 3.14
            // 错误转换,抛出异常
            int i = std::any_cast<int>(a2);
        }
        catch (const std::bad_any_cast& e)
        {
            std::cerr << "Cast error: " << e.what() << std::endl;
            // Cast error: Bad any_cast
        }

        // 使用指针转换版本,不抛出异常。
        if (double* ptr = std::any_cast<double>(&a2))
        {
			cout << "Pointer cast value: " << *ptr << endl;
        }
        else
        {
			cout << "Pointer cast failed." << endl;
        }
        // Pointer cast value: 3.14
    }

示例2:std::any在容器中的应用,any存储不同的类型,然后使用any_cast<>转换并获取对应类型的数据。

cpp 复制代码
    struct Point
    {
        int x;
        int y;
    };

    void test()
    {
		// 容器存储不同类型的值
		std::vector<std::any> vec;
        vec.push_back(33);
		vec.emplace_back(std::string("Hello"));
		vec.emplace_back(Point{ 10,20 });

        // 访问
        for (const auto& item : vec)
        {
            if (item.type() == typeid(int))
            {
                std::cout << "整数: " << std::any_cast<int>(item) << std::endl;
            }
            else if (item.type() == typeid(std::string))
            {
                std::cout << "字符串: " << std::any_cast<std::string>(item) << std::endl;
            }
            else if (item.type() == typeid(Point))
            {
                Point p = std::any_cast<Point>(item);
				std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl;
            }
        }
        /*
            整数: 33
            字符串: Hello
            Point: (10, 20)
        */
    }

2.3.3 any 和 variant 对比

1 any和variant都是类型安全,自动管理生命周期,支持任意可拷贝构造的类型。

2 性能开销比variant大,类型转换必须精确匹配。

3 如果预先知道类型是固定的,优先使用std::variant;如果未知,可使用std::any.

2.4 std::string_view

C++17之前我们在编写带有字符串参数的函数时,参数类型通常使用 const std::string & 常量引用,直接访问原始string对象,避免拷贝传入的string。但是缺点是,如果传入的是C风格的字符串,比如"hello",编译器必须隐士创建一个临时的std::string对象,然后再传入引用,这就造成了性能开销。

C++17中使用std::string_view 解决上面传递临时对象开辟内存的问题。

2.4.1 实现原理

std::string_view包含在头文件<string_view>中,实现原理:

1 指针 (ptr):指向字符串数据的起始位置(可以是 std::string 的 buffer,也可以是 C 风格字符串字面量,或者是 vector 的 buffer)。

2 长度 (size):记录字符串的长度。

实现源码大概如下:

cpp 复制代码
// 伪代码
template <class CharT>
class basic_string_view {
    const CharT* data_;
    size_t size_;
    // ... 提供了类似 std::string 的只读接口
};

2.4.2 使用示例

示例1:替代const std::string & 作为参数

cpp 复制代码
    // 传统写法:传入 "Hello" 时会产生临时 std::string 对象及堆分配
    void printStringOld(const std::string& str)
    {
        std::cout << str << std::endl;
    }

    // C++17 写法:无论传 std::string 还是 "Hello",都没有堆分配
    void printStringNew(std::string_view sv) 
    {
        std::cout << sv << std::endl;
    }

    void test()
    {
		const char* cstring = "c风格字符串";
		std::string cppstring = "C++字符串";
		
        // 1. 接受 C 风格字符串 (零开销)
        printStringNew(cstring);
        // c风格字符串
        printStringNew("Literal String");
        // Literal String

        // 2. 接受 std::string (零开销,自动隐式转换)
        printStringNew(cppstring);
        // C++字符串
    }

示例2:高效的子串操作

string的对象调用substr时会创建一个新的字符串,而使用std::string_view 调用substr仅仅调整内部的指针和长度,时间复杂度O(1),零拷贝。

cpp 复制代码
    void test()
    {
		std::string_view sv = "Hello, std::string_view!";

        std::string_view sub = sv.substr(1, 5);
		cout << "子串: " << sub << endl;
        // 子串: ello,
    }

示例3:生命周期管理注意

由于string_view不拥有数据,只是对原始数据的操作,所以必须确保原始数据有效。

cpp 复制代码
	// 错误: temp字符串在函数结束时被销毁,返回的string_view悬空
    std::string_view getBadView()
    {
		string temp = "临时字符串";
		return std::string_view(temp); //
    }

    void test()
    {
		std::string_view ret = getBadView();
        std::cout << "悬空视图内容: " << ret << std::endl;
		// 悬空视图内容: 烫烫?

        std::string str = "Hello";
        std::string_view sv = str;

        str += " World"; // str 可能重新分配内存
		std::cout << "视图内容: " << sv << std::endl;
        // 视图内容: Hello
		// sv 仍指向旧内存,str内部的buffer已经更新,sv内容未变
    }

使用总结:

1 使用时,直接传递string_view,不用传递 string_view 引用。

2 之后使用substr时,使用string_view的substr,避免拷贝,提高性能。

相关推荐
水天需0102 小时前
C++ 三种指针转换深度解析
c++
言言的底层世界3 小时前
c++中STL容器及算法等
开发语言·c++·经验分享·笔记
Mr_WangAndy3 小时前
C++17 新特性_第一章 C++17 语言特性___has_include,u8字符字面量
c++·c++40周年·c++17新特性·__has_include·u8字面量
liu****3 小时前
八.函数递归
c语言·开发语言·数据结构·c++·算法
Vanranrr4 小时前
C++临时对象与悬空指针:一个导致资源加载失败的隐藏陷阱
服务器·c++·算法
BestOrNothing_20154 小时前
【C++基础】Day 5:struct 与 class
c++·c·class类·struct结构体·typename模板·private与public
枫叶丹44 小时前
【Qt开发】Qt窗口(三) -> QStatusBar状态栏
c语言·开发语言·数据库·c++·qt·microsoft
Skrrapper4 小时前
【编程史】微软的起家之路:一代传奇的诞生
数据库·c++·microsoft
Super小白&5 小时前
C++ 高可用线程池实现:核心 / 非核心线程动态扩缩容 + 任务超时监控
c++·线程池