是否提倡使用std::any?
别用 std::any
,用微软的 proxy。
刚接触 C++17 的时候我以为 std::any
是个什么高大上的黑魔法,然后发现好像没啥实际用处,而且性能差得一匹。
说回 std::any
,你说它替代 void*
吧,它还真替代不了。不光运行时性能远低于用 void*
传参,我要转个基类类型它都没法转,要它何用?
不光没用,你要不小心用了还可能在背后给你惹一堆破事。不管编译器怎么优化,总有一些情况 std::any
绕不过申请堆内存。
因为 std::any
的一个对象就这么大,能塞的东西肯定是有限的,如果塞的东西比 std::any
大,那会被塞到哪里就可想而知了;而且不同平台 std::any
给的缓冲区大小还不一样(从 16 字节到 64 字节都有),性能根本不可控。
也能理解这些编译器厂商为什么不喜欢标准委员会了。
你说这个缓冲区给小点吧,稍大点的对象就得申请堆内存;给大点吧,一个 char
哪怕只有一个字节 sizeof(std::any)
也那么大。
其他异常、RTTI 什么的就不说了,一个个全是性能杀手。
什么?你的项目把异常和 RTTI 禁了?对不起,用不了。
后来我用过微软出的 proxy
之后(好像快进标准了),感觉不止 std::any
,虚函数, std::function
(C++11), std::move_only_function
(C++23), std::copyable_function
(C++26) 统统都是 C++ 走过的弯路。
用 proxy
可以一行就实现一个 std::any
,而且缓冲区大小随便定(没指定就用默认的两个指针大小),而且还不强制要求用 RTTI。
PRO_DEF_FACADE(MyAny);
pro::proxy<MyAny> obj = pro::make_proxy<MyAny>(123);
proxy
能做的事情比这多多了,虚函数能干的它都能干,虚函数干不了的它也能干。
实现个 std::function
,std::move_only_function
什么的也是简简单单,GitHub 上有例子,还可以支持多种重载。
// 定义抽象
namespace spec {
template <class... Overloads>
PRO_DEF_FREE_DISPATCH(Call, std::invoke, Overloads...);
template <class... Overloads>
PRO_DEF_FACADE(MovableCallable, Call<Overloads...>);
template <class... Overloads>
PRO_DEF_FACADE(CopyableCallable, Call<Overloads...>, pro::copyable_ptr_constraints);
} // namespace spec
// MyFunction 实现了 std::function 的主要功能且支持任意多种重载
// MyMoveOnlyFunction 实现了 std::move_only_function but 的主要功能且支持任意多种重载
template <class... Overloads>
using MyFunction = pro::proxy<spec::CopyableCallable<Overloads...>>;
template <class... Overloads>
using MyMoveOnlyFunction = pro::proxy<spec::MovableCallable<Overloads...>>;
int main() {
auto f = [](auto&&... v) {
printf("f() called. Args: ");
((std::cout << v << ":" << typeid(decltype(v)).name() << ", "), ...);
puts("");
};
MyFunction<void(int)> p0{&f};
p0(123); // 输出 "f() called. Args: 123:i," (assuming GCC)
MyMoveOnlyFunction<void(), void(int), void(double)> p1{&f};
p1(); // 输出 "f() called. Args:"
p1(456); // 输出 "f() called. Args: 456:i,"
p1(1.2); // 输出 "f() called. Args: 1.2:d,"
return 0;
}
里面那个 pro::copyable_ptr_constraints
可以改缓冲区大小,甚至还能做编译时反射,回头看 std::any
就是个残废