std::any 是 C++17 引入的一个'类型擦除(type erasure)'容器,用来 在运行期存放'任意类型的一个值',并在需要时再把它安全地取出来。
你可以把它理解成:
std::any 是 C++17 引入的类型擦除容器,用于在运行期存放任意类型的值。文章介绍了其构造方式(空、拷贝、移动、直接赋值、就地构造),特别强调了 constexpr 默认构造函数对避免静态初始化顺序问题的意义。内容涵盖主要成员函数如 emplace、reset、swap、has_value、type,以及非成员函数 any_cast 和 make_any 的使用方法和异常处理 bad_any_cast。通过代码示例展示了 lambda 表达式存储时的类型匹配问题及解决方案。

std::any 是 C++17 引入的一个'类型擦除(type erasure)'容器,用来 在运行期存放'任意类型的一个值',并在需要时再把它安全地取出来。
你可以把它理解成:
void* + RAII + RTTI'std::any 的构造函数可以分成 三大类:
它们的共同约束只有一句话:
std::any存的东西必须是可拷贝构造的
// 构造一个空的 any。它是 constexpr 的,静态 std::any 在动态初始化开始前就完成初始化
constexpr any() noexcept;
// other 为空则新对象也为空;other 有值则复制一份'同类型'的对象
any( const any& other );
any( any&& other ) noexcept;
// 构造对象,其初始内容为 std::decay_t<ValueType> 类型对象
// std::any 永远存值,不存引用
template< class ValueType >
any( ValueType&& value );
// 就地构造,无中间对象
template< class ValueType, class... Args >
explicit any( std::in_place_type_t<ValueType>, Args&&... args );
template< class ValueType, class U, class... Args >
explicit any( std::in_place_type_t<ValueType>, std::initializer_list<U> il, Args&&... args );
std::any a = "10"; // const char *
std::any b = std::string("10"); // std::string
std::any c(std::in_place_type<std::string>, 10, 'x'); // 类型是 string,存的内容是 10 个 x
in_place_type_t 是什么?#include <utility>
template<class T>
struct in_place_type_t { };
template<class T>
inline constexpr in_place_type_t<T> in_place_type{};
它不是对象本身,它是一个'类型标签(tag)'。
| 名字 | 本质 |
|---|---|
std::in_place_type_t<T> | 一个类型 |
std::in_place_type<T> | 这个类型的一个 constexpr 对象 |
使用的时候一般都写 std::in_place_type<T>。
constexpr 默认构造函数的意义?因为 std::any 的默认构造是 constexpr,所以'全局 / 静态的 std::any 对象'一定会在程序真正开始运行前就被安全地构造完成,不会踩到'静态初始化顺序问题'。
std::any g_any; // 全局对象
struct X {
static std::any a;
};
这些都叫:static storage duration(静态存储期) 且是 non-local(非局部)。
它们的生命周期是:
包括:
constexpr 构造constexpr 默认构造有什么特殊?std::any 的默认构造是:constexpr。它可以在编译期完成初始化,即它是静态初始化的。
// a.cpp
std::string g_str = "hello";
// b.cpp
extern std::string g_str;
struct X {
X() {
std::cout << g_str << std::endl; // ❌ 可能还没构造
}
};
X x;
⚠️ g_str 是 动态初始化
⚠️ 跨编译单元顺序未定义
⚠️ UB
换成 std::any
// a.cpp
std::any g_any;
// b.cpp
extern std::any g_any;
struct X {
X() {
if (g_any.has_value()) { // ✅ 安全 ...
}
}
};
X x;
👉 这是安全的
因为:
g_any 在程序加载阶段就已经完成构造因为 std::any 是一个'类型擦除容器',你可能会担心:
std::string 一样有动态初始化?标准在这里明确保证:
默认构造的
std::any非常'轻',可以放心作为全局对象使用
template< class ValueType, class... Args >
std::decay_t<ValueType>& emplace( Args&&... args );
template< class ValueType, class U, class... Args >
std::decay_t<ValueType>& emplace( std::initializer_list<U> il, Args&&... args );
更改所含对象为从参数构造的 std::decay_t<ValueType> 类型对象。
注意首先要用 reset() 销毁当前所含对象(若存在)。
ValueType: 所含的值类型到新的所含对象的引用。
抛出 T 构造函数所抛的任何异常。若抛出异常,则销毁先前所含对象(若存在),而 *this 不含值。
#include <algorithm>
#include <any>
#include <iostream>
#include <string>
#include <vector>
class Star {
std::string name;
int id;
public:
Star(std::string name, int id) : name { name }, id { id } {
std::cout << "Star::Star(string, int)\n";
}
void print() const {
std::cout << "Star{ \"" << name << "\" : " << id << " };\n";
}
};
auto main() -> int {
std::any celestial; // (1)
celestial.emplace<Star>("Procyon", 2943);
const auto* star = std::any_cast<Star>(&celestial);
star->print();
std::any av; // (2)
av.emplace<std::vector<char>>({ 'C', '+', '+', '1', '7' });
std::cout << av.type().name() << '\n';
const auto* va = std::any_cast<std::vector<char>>(&av);
std::for_each(va->cbegin(), va->cend(), [](char const& c) {
std::cout << c;
});
std::cout << '\n';
}
输出
Star::Star(string, int)
Star{ "Procyon" : 2943 };
St6vectorIcSaIcEE
C++17
void reset() noexcept;
若非空,则销毁所含对象。
void swap(any& other) noexcept;
交换二个 any 对象的内容。
bool has_value() const noexcept;
检查对象是否含有值。若实例含值则为 true,否则为 false。
#include <any>
#include <iostream>
#include <string>
int main() {
std::boolalpha(std::cout);
std::any a0;
std::cout << "a0.has_value(): " << a0.has_value() << "\n";
std::any a1 = 42;
std::cout << "a1.has_value(): " << a1.has_value() << '\n';
std::cout << "a1 = " << std::any_cast<int>(a1) << '\n';
a1.reset();
std::cout << "a1.has_value(): " << a1.has_value() << '\n';
auto a2 = std::make_any<std::string>("Milky Way");
std::cout << "a2.has_value(): " << a2.has_value() << '\n';
std::cout << "a2 = \"" << std::any_cast<std::string&>(a2) << "\"\n";
a2.reset();
std::cout << "a2.has_value(): " << a2.has_value() << '\n';
}
输出:
a0.has_value(): false
a1.has_value(): true
a1 = 42
a1.has_value(): false
a2.has_value(): true
a2 = "Milky Way"
a2.has_value(): false
const std::type_info& type() const noexcept;
查询所含类型。若实例非空则为所含值的 typeid,否则为 typeid(void)。
(1) template<class T> T any_cast(const any& operand);
(2) template<class T> T any_cast(any& operand);
(3) template<class T> T any_cast(any&& operand);
(4) template<class T> const T* any_cast(const any* operand) noexcept;
(5) template<class T> T* any_cast(any* operand) noexcept;
进行对所含有对象的类型安全访问。
令 U 为 std::remove_cv_t<std::remove_reference_t<T>>。(将 T 类型移除引用和 const)
operand: 目标 any 对象1-2) 返回 static_cast<T>(*std::any_cast<U>(&operand))。
static_cast<T>(std::move(*std::any_cast<U>(&operand)))。4-5) 若 operand 不是空指针,且请求的 T 的 typeid 匹配 operand 的 typeid,则为指向所含值的指针,否则为空指针。
1-3) 若请求的 T 的 typeid 不匹配 operand 内容的 typeid,则抛出 std::bad_any_cast。
#include <string>
#include <iostream>
#include <any>
#include <utility>
int main() {
// 简单示例
auto a = std::any(12);
std::cout << std::any_cast<int>(a) << '\n';
try {
// 这里不匹配,会抛出异常
std::cout << std::any_cast<std::string>(a) << '\n';
} catch(const std::bad_any_cast& e) {
std::cout << e.what() << '\n';
}
// 指针示例
if (int* i = std::any_cast<int>(&a)) {
std::cout << "a is int: " << *i << '\n';
} else if (std::string* s = std::any_cast<std::string>(&a)) {
std::cout << "a is std::string: " << *s << '\n';
} else {
std::cout << "a is another type or unset\n";
}
// 进阶示例
a = std::string("hello");
auto& ra = std::any_cast<std::string&>(a); //< 引用
ra[1] = 'o';
std::cout << "a: " << std::any_cast<const std::string&>(a) << '\n'; //< const 引用
auto b = std::any_cast<std::string&&>(std::move(a)); //< 右值引用
// 注意:'b' 是移动构造的 std::string,'a' 被置于合法但未指定的状态
std::cout << "a: " << *std::any_cast<std::string>(&a) << "\n";
std::cout << "b: " << b << '\n';
}
输出:
12
bad any_cast
a is int: 12
a: hollo
a:
b: hollo
//(1) template< class T, class... Args > std::any make_any( Args&&... args );
// (2) template< class T, class U, class... Args > std::any make_any( std::initializer_list<U> il, Args&&... args );
构造含 T 类型对象的 any 对象,传递提供的参数给 T 的构造函数。
等价于 return std::any(std::in_place_type<T>, std::forward<Args>(args)...);
等价于 return std::any(std::in_place_type<T>, il, std::forward<Args>(args)...);
#include <any>
#include <complex>
#include <functional>
#include <iostream>
#include <string>
int main() {
auto a0 = std::make_any<std::string>("Hello, std::any!\n");
auto a1 = std::make_any<std::complex<double>>(0.1, 2.3);
std::cout << std::any_cast<std::string&>(a0);
std::cout << std::any_cast<std::complex<double>&>(a1) << '\n';
using lambda = std::function<void(void)>;
// 把 lambda 放入 std::any。尝试 #1(失败)。
std::any a2 = [] { std::cout << "Lambda #1.\n"; };
std::cout << "a2.type() = \"" << a2.type().name() << "\"\n";
// any_cast 转型到 <void(void)> 但实际类型不是 std::function ……,而是 ~ main::{lambda()#1},且它对每个 lambda 唯一。所以这会抛出……
try {
std::any_cast<lambda>(a2)();
} catch (std::bad_any_cast const& ex) {
std::cout << ex.what() << '\n';
}
// 将 lambda 放入 std::any 中。尝试 #2(成功)
auto a3 = std::make_any<lambda>([] { std::cout << "Lambda #2.\n"; });
std::cout << "a3.type() = \"" << a3.type().name() << "\"\n";
std::any_cast<lambda>(a3)();
}
输出
Hello, std::any!
(0.1,2.3)
a2.type() = "Z4mainEUlvE_"
bad any_cast
a3.type() = "St8functionIFvvEE"
Lambda #2.
为什么#1会抛异常呢,因为 lambda 表达式的类型不等于 std::function,它是一个编译器生成的、匿名的、唯一的类型:
struct <unique_lambda_type> {
void operator()() const;
};
any_cast<lambda> 中需要计算类型 T 和 U:
T = std::function<void(void)>
U = remove_cvref_t<T> = std::function<void(void)>
然后检查 typeid 是否一致:
typeid(<unique_lambda_type>) == typeid(std::function<void(void)>) ?
这里是不等的,所以会抛出异常。
class bad_any_cast : public std::bad_cast;
定义 std::any_cast 在失败时以值返回形式抛出的对象的类型。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online