🫛std::any|C++17

std::any 是 C++ 标准库在 C++17 中引入的一项功能。它属于 C++ 标准库中的类型安全容器类,提供了一种可以类型安全地存储和操作任意类型值的方法。这在需要处理异构对象集合或在编译时未知对象类型的情况下尤其有用。

std::any 是一个包装类,是 void* 的一个理想替代品。在 C++17 标准之前编写的代码中,any 类可以替代许多使用 void* 类型的地方。众所周知,类型为 void* 的指针可以保存任何类型对象的地址。但类型为 void* 的指针并不知道它所持有对象的类型,也无法控制其生命周期。C++17 引入了 std::any,作为 void* 的理想替代品。void* 不同,std::any 可以控制其所持有对象的生命周期,并且始终知道其所持有对象的类型。

那么,std::any 在哪些地方使用呢?任何使用 void* 的地方都可以使用 std::any

std::any 不是类模板。当我们创建一个 std::any 对象时,不需要指定它将持有的值的类型。一个 std::any 对象可以持有任意类型的值,同时也知道它包含的值的类型。但这是如何实现的呢?一个对象怎么能存储任意类型的值呢?秘诀在于 std::any 对象除了存储实际值外,还包含该值的 type_info

要使用 std::any,必须包含 <any> 头文件。成员函数、辅助类和非成员函数如下。

std::any 对象可以创建来持有特定类型的值,也可以为空:

#include <iostream>
#include <any>
#include <bitset>
#include <vector>

int main()
{
    std::any any1 = 10;                              /* int */
    std::any any2{ 1.3 };                            /* double */
    std::any any3{ "Cengizhan" };                    /* const char* */
    std::any any4{ std::string("Cengizhan") };       /* std::string*/
    std::any any5{ std::vector<char>{'a','b','c'} }; /* std::vector */
    std::any any6{};                                 /* empty */
    std::any any7;                                   /* empty */

    return 0;
}

就像 std::optional 一样,std::variant 可以在创建对象时使用 std::in_place_type 构造。同样,也可以使用 std::make_any<T> 工厂函数。

std::in_place_type 是 C++17 中引入的一个功能,用于在 std::variantstd::optional 和其他类似模板类中,以原位构造对象。这意味着对象是在其最终存储位置直接构造的,而不是先构造在临时位置然后移动或复制到最终位置。这样可以提高效率,避免不必要的拷贝或移动操作。

std::make_any

另一种创建 any 类型对象的方法是使用 make_any<> 辅助工厂函数。

std::any a3 = std::make_any<bool>(true);  // make_any

Memory requirement for std::any objects

std::any 对象的内存需求 std::any<T> 使用额外的内存,因为它不知道要使用或分配哪种类型。然而,为了提升性能,它采用了 SBO(Small Buffer Optimization,小缓冲优化)方法。因此,sizeof(std::any) 超过了它所存储类型的大小,并且取决于编译器对 Small Buffer Optimization 的实现。

std::cout << "std::any sizeof: " << sizeof(std::any);

输出:“std::any sizeof: 16”

我的 C++ 编译器是 gcc 11.4。运行上述代码时,可能会因编译器的不同而得到不同的值。管理 std::any 的内存需求是一个非常动态的过程。std::any 类型会根据其存储的数据的大小和类型动态分配和释放内存。

如何为 std::any 对象分配新值?

any 类对象的值可以通过类的赋值操作符函数或 emplace<> 函数来更改。

#include <string>
#include <any>
#include <vector>

int main()
{
    std::any any;
    auto lmb = [](int x)->int{ return x*x; };

    any = 123;
    any = "cengizhan";
    any.emplace<std::string>("Cengizhan");
    any.emplace<std::vector<int>>(5);
    any.emplace<decltype(lmb)>( lmb );
}

如何清空 std::any

要清空 std::any 对象,可以调用该类的 reset 函数:

any.reset();

通过调用这个函数,如果类型为 any 的变量 a 非空,那么变量 a 所持有的对象的生命周期将会终止。完成这个过程后,变量 a 变为空。

我们也可以通过将使用默认构造函数创建的临时对象赋值给变量来执行清空操作。

any = {}; 

std::any 的观察者函数 type()has_value() 函数是 std::any 的观察者函数。

我们可以通过 has_value() 函数检查 std::any 对象是否为空。

#include <iostream>
#include <string>
#include <any>
#include <vector>

int main()
{
    std::any any;
    std::cout<<std::boolalpha<<any.has_value()<<"\n";

    any = "cengizhan";
    std::cout<<std::boolalpha<<any.has_value()<<"\n";

    any.reset();
    std::cout<<std::boolalpha<<any.has_value()<<"\n";
}

// false
// true
// false
const std::type_info& type() const noexcept;

type() 函数的定义如上所示。

通过 type() 函数,可以了解 std::any 持有的对象的类型。

int main()
{
    std::any any;

    std::cout << std::boolalpha<<(any.type() == typeid(void)) << '\n';

    any = 15;

    std::cout << std::boolalpha<<(any.type() == typeid(int)) << '\n';
}

std::any_cast 函数

C++ 中的 std::any_cast 函数用于安全地提取和转换 std::any 对象内存储的值。std::any 是一个类型安全的容器,可以持有任意类型的值。要访问 std::any 内的值,可以使用 std::any_cast

#include <iostream>
#include <any>

int main() {

    std::any any = 3;

    try {
       std::cout << "Value inside any: " << std::any_cast<int>(any) << std::endl;
    } 
    catch (const std::bad_any_cast& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

如果类型转换失败?当然会抛出异常。

std::bad_any_cast

如果使用 any_cast<> 进行类型转换失败,会抛出一个 bad_any_cast 类型的异常:

#include <iostream>
#include <any>

int main() {

    std::any any = 3;

    try {
        std::cout << "Value inside any: " << std::any_cast<std::string>(any) << std::endl;
    } catch (const std::bad_any_cast& e) {
        std::cerr << "Error: " << e.what() << std::endl;
    }

    return 0;
}

// Value inside any: Error: bad any_cast

案例

  • 配合函数对象

#include <iostream>
#include <any>
#include <functional>

// 普通函数
void say_hello() {
    std::cout << "Hello, World!" << std::endl;
}

// 函数对象(仿函数)
struct Functor {
    void operator()() const {
        std::cout << "Hello from Functor!" << std::endl;
    }
};

int main() {
    // 使用 std::any 存储普通函数
    std::any a1 = say_hello;
    // 使用 std::any 存储仿函数对象
    std::any a2 = Functor();
    // 使用 std::any 存储 lambda 表达式
    std::any a3 = [] { std::cout << "Hello from Lambda!" << std::endl; };

    // 调用存储的普通函数
    std::any_cast<void(*)()>(a1)();

    // 调用存储的仿函数对象
    std::any_cast<Functor>(a2)();

    // 调用存储的 lambda 表达式
    std::any_cast<std::function<void()>>(a3)();

    return 0;
}
#include <iostream>
#include <any>
#include <string>

void printValue(const std::any& data) {
    if (data.type() == typeid(int)) {
        std::cout << "Value: " << std::any_cast<int>(data) << std::endl;
    } else if (data.type() == typeid(double)) {
        std::cout << "Value: " << std::any_cast<double>(data) << std::endl;
    } else if (data.type() == typeid(std::string)) {
        std::cout << "Value: " << std::any_cast<std::string>(data) << std::endl;
    } else {
        std::cout << "Unknown type" << std::endl;
    }
}

int main() {
    std::any intValue = 42;
    std::any doubleValue = 3.14;
    std::any stringValue = std::string("Hello, World");

    printValue(intValue);
    printValue(doubleValue);
    printValue(stringValue);

    return 0;
}

使用 void* 的代码

传统上,如果你想要存储任意类型的值并在运行时处理它们,你可能会使用 void*type_info 来实现。这种方法需要手动管理类型信息,并且容易出错。

#include <iostream>
#include <string>
#include <typeinfo>

void printValue(void* data, const std::type_info& type) {
    if (type == typeid(int)) {
        std::cout << "Value: " << *static_cast<int*>(data) << std::endl;
    } else if (type == typeid(double)) {
        std::cout << "Value: " << *static_cast<double*>(data) << std::endl;
    } else if (type == typeid(std::string)) {
        std::cout << "Value: " << *static_cast<std::string*>(data) << std::endl;
    } else {
        std::cout << "Unknown type" << std::endl;
    }
}

int main() {
    int intValue = 42;
    double doubleValue = 3.14;
    std::string stringValue = "Hello, World";

    printValue(&intValue, typeid(int));
    printValue(&doubleValue, typeid(double));
    printValue(&stringValue, typeid(std::string));

    return 0;
}

使用 std::any 的代码

使用 std::any 可以简化这一过程,提供类型安全的存储和访问,并且不需要手动管理类型信息。

#include <iostream>
#include <any>
#include <string>

void printValue(const std::any& data) {
    if (data.type() == typeid(int)) {
        std::cout << "Value: " << std::any_cast<int>(data) << std::endl;
    } else if (data.type() == typeid(double)) {
        std::cout << "Value: " << std::any_cast<double>(data) << std::endl;
    } else if (data.type() == typeid(std::string)) {
        std::cout << "Value: " << std::any_cast<std::string>(data) << std::endl;
    } else {
        std::cout << "Unknown type" << std::endl;
    }
}

int main() {
    std::any intValue = 42;
    std::any doubleValue = 3.14;
    std::any stringValue = std::string("Hello, World");

    printValue(intValue);
    printValue(doubleValue);
    printValue(stringValue);

    return 0;
}

总结

std::any是一个可以存储任何可拷贝类型的容器,C 语言中通常使用void*实现类似的功能,与void*相比,std::any具有两点优势:

  1. std::any更安全:在类型 T 被转换成void*时,T 的类型信息就已经丢失了,在转换回具体类型时程序无法判断当前的void*的类型是否真的是 T,容易带来安全隐患。std::any会存储类型信息,std::any_cast是一个安全的类型转换。

  2. std::any管理了对象的生命周期,在std::any析构时,会将存储的对象析构,而void*则需要手动管理内存。

std::any应当很少是程序员的第一选择,在已知类型的情况下,std::optional, std::variant和继承都是比它更高效、更合理的选择。只有当对类型完全未知的情况下,才应当使用std::any,比如动态类型文本的解析或者业务逻辑的中间层信息传递。

reference

Last updated