【发布时间】:2012-06-25 11:09:44
【问题描述】:
我想知道unordered_map 是否使用类型擦除来实现,因为unordered_map<Key, A*> 和unordered_map<Key, B*> 可以使用完全相同的代码(除了强制转换,这是机器代码中的无操作)。即两者的实现都可以基于unordered_map<Key, void*>来节省代码大小。
更新:这种技术通常被称为Thin Template Idiom(感谢下面的评论者指出这一点)。
更新 2: 我对 Howard Hinnant 的意见特别感兴趣。让我们希望他能读到这篇文章。
所以我写了这个小测试:
#include <iostream>
#if BOOST
# include <boost/unordered_map.hpp>
using boost::unordered_map;
#else
# include <unordered_map>
using std::unordered_map;
#endif
struct A { A(int x) : x(x) {} int x; };
struct B { B(int x) : x(x) {} int x; };
int main()
{
#if SMALL
unordered_map<std::string, void*> ma, mb;
#else
unordered_map<std::string, A*> ma;
unordered_map<std::string, B*> mb;
#endif
ma["foo"] = new A(1);
mb["bar"] = new B(2);
std::cout << ((A*) ma["foo"])->x << std::endl;
std::cout << ((B*) mb["bar"])->x << std::endl;
// yes, it leaks.
}
并通过各种设置确定编译输出的大小:
#!/bin/sh
for BOOST in 0 1 ; do
for OPT in 2 3 s ; do
for SMALL in 0 1 ; do
clang++ -stdlib=libc++ -O${OPT} -DSMALL=${SMALL} -DBOOST=${BOOST} map_test.cpp -o map_test
strip map_test
SIZE=$(echo "scale=1;$(stat -f "%z" map_test)/1024" | bc)
echo boost=$BOOST opt=$OPT small=$SMALL size=${SIZE}K
done
done
done
事实证明,在我尝试的所有设置下,unordered_map 的大量内部代码似乎被实例化了两次:
With Clang and libc++:
| -O2 | -O3 | -Os
-DSMALL=0 | 24.7K | 23.5K | 28.2K
-DSMALL=1 | 17.9K | 17.2K | 19.8K
With Clang and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 23.9K | 23.9K | 32.5K
-DSMALL=1 | 17.4K | 17.4K | 22.3K
With GCC and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 21.8K | 21.8K | 35.5K
-DSMALL=1 | 16.4K | 16.4K | 26.2K
(使用 Apple Xcode 的编译器)
现在的问题是:是否有一些令人信服的技术原因导致实施者选择省略这个简单的优化?
另外:为什么-Os 的效果与宣传的完全相反?
更新 3:
按照 Nicol Bolas 的建议,我使用 shared_ptr<void/A/B> 而不是裸指针(使用 make_shared 创建并使用 static_pointer_cast 强制转换)重复测量。结果的趋势是一样的:
With Clang and libc++:
| -O2 | -O3 | -Os
-DSMALL=0 | 27.9K | 26.7K | 30.9K
-DSMALL=1 | 25.0K | 20.3K | 26.8K
With Clang and Boost:
| -O2 | -O3 | -Os
-DSMALL=0 | 35.3K | 34.3K | 43.1K
-DSMALL=1 | 27.8K | 26.8K | 32.6K
【问题讨论】:
-
'Type-erasure' 在涉及 C++ 时往往指的是一种非常特殊的技术,但这里不是这样。据我所知,尽管它是一种已知技术,但根据另一种专业化(或通用实现)编写部分专业化的技术没有特定的名称。 (不过有人确实建议使用“薄模板”。)
-
您是否考虑过由于生命周期和所有权问题,将裸指针存储在标准库容器中通常不是一个好主意?良好的现代 C++ 编程实践建议使用
unique_ptr、shared_ptr或其他形式的智能指针,而不是裸指针。此时您的瘦模板“优化”的价值为零。 -
很奇怪,但我很满意(并且不是很惊讶)看到可执行文件的大小总是更小,当使用优化速度编译时,比使用优化尺寸编译时。
-
@NicolBolas:当然,智能指针更好,但为了论证,我简化了问题。智能指针仍然可以进行相同的优化,只需将您的实现基于
some_ptr<void>并使用static_pointer_cast。见上文。 -
@NicolBolas:当然,您不能转换
unique_ptrs,但您可以转换底层原始指针。当然,所有智能指针类型都必须这样做,但这可以通过模板模板和enable_if来检测(支持的)智能指针来完成。同样,这并不是为了让库实现者的生活更轻松,而是为了让库用户更轻松。在我的应用程序中,我确实需要许多不同指针类型的unordered_maps。
标签: c++ boost unordered-map type-erasure libc++