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,避免拷贝,提高性能。

相关推荐
仰泳的熊猫29 分钟前
1109 Group Photo
数据结构·c++·算法·pat考试
SunkingYang29 分钟前
MFC中事件与消息有什么关联,区别与联系
c++·mfc·消息·事件·区别·联系·关联
青山是哪个青山36 分钟前
第二节:CMake 命令行工具与工程生命周期
c++·cmake
ozyzo40 分钟前
局部变量和全局变量
c++
夏幻灵1 小时前
C++ 里 什么时候不用指针,而选择值拷贝/深拷贝 ?
开发语言·c++·算法
superman超哥1 小时前
仓颉语言中字典的增删改查:深度剖析与工程实践
c语言·开发语言·c++·python·仓颉
青山是哪个青山1 小时前
第一节:CMake 简介
linux·c++·cmake
晨晖21 小时前
二叉树遍历,先中后序遍历,c++版
开发语言·c++
M__332 小时前
动规入门——斐波那契数列模型
数据结构·c++·学习·算法·leetcode·动态规划
wangchen_02 小时前
C/C++时间操作(ctime、chrono)
开发语言·c++