空别名 shared_ptr 是无操作删除 shared_ptr 的一个很好的替代方案吗?
Posted
技术标签:
【中文标题】空别名 shared_ptr 是无操作删除 shared_ptr 的一个很好的替代方案吗?【英文标题】:Is an empty aliasing shared_ptr a good alternative to a no-op deleting shared_ptr? 【发布时间】:2015-07-26 12:19:09 【问题描述】:有时我需要具有无操作删除器的 shared_ptr
实例,因为 API 需要一个 shared_ptr
实例,它想在有限的时间内存储它,但我得到了一个我不允许拥有的原始指针比我要争取的时间更长。
对于这种情况,我一直在使用无操作删除器,例如[](const void *)
,但今天我发现还有另一种替代方法,使用(或滥用?)aliasing constructor 的shared_ptr
:
void f(ExpectedClass *ec)
std::shared_ptr<ExpectedClass> p(std::shared_ptr<void>(), ec);
assert(p.use_count() == 0 && p.get() != nullptr);
apiCall(p);
我的问题是,有什么更好的方法来做到这一点,为什么?性能预期是否相同?对于无操作删除器,我希望为删除器和引用计数的存储支付一些成本,而在使用带有空 shared_ptr
的别名构造函数时似乎并非如此。
【问题讨论】:
是的,在 [util.smartptr.shared.const]/16 中有一个注释提到了这个奇怪的地方。恕我直言,这是您可以使用非空shared_ptr
存储空值(shared_ptr<T>((T*)nullptr)
)这一事实的必然结果。可能有点令人困惑,但有效。这种用法的一个问题是p
的副本不与它共享所有权,因为它们都不拥有任何东西。这对于希望检查对象共享所有权(通过owner_less
)的某些用途可能很重要,但对于您描述的用途可能不是问题。
@JonathanWakely 感谢您的留言。它之前的注释/15)让我感到困惑。为什么用户必须确保p
一直有效,直到r
的所有权组在shared_ptr<>(r, p)
中被销毁?我认为这是shared_ptr
的业务。如果我只是将它存储起来,别名指针将始终有效,因为它可以防止 r
的所有权组被破坏,以便用户可以“复制并忘记”它,还是我错过了什么?
啊,我明白它的意思了。如果p
的生命周期不绑定r
的生命周期(这是允许的),那么r
的生命周期并不意味着p
的生命周期,那么用户必须保证这一点。
别名构造函数是noexcept
,shared_ptr
的默认ctor也是如此。没有内存分配。
@JohannesSchaub-litb,是的,我相信结果是等价的。对于第一个问题,您的后续评论完全正确。考虑struct A int i; ; void foo() auto p1 = make_shared<A>(); shared_ptr<int> p2(p1, p1->i); int i; shared_ptr<int> p3(p1, i); return p3;
,这里p2
满足该要求,只要p1
的所有权组有效,p1->i
就有效,但p3
不满足该要求,局部变量i
之前超出范围所有权组的使用计数达到零,导致 *foo()
未定义行为。这就是 /15 中的注释的意思。
【参考方案1】:
关于性能,以下基准显示不稳定的数据:
#include <chrono>
#include <iostream>
#include <limits>
#include <memory>
template <typename... Args>
auto test(Args&&... args)
using clock = std::chrono::high_resolution_clock;
auto best = clock::duration::max();
for (int outer = 1; outer < 10000; ++outer)
auto now = clock::now();
for (int inner = 1; inner < 20000; ++inner)
std::shared_ptr<int> sh(std::forward<Args>(args)...);
auto time = clock::now()-now;
if (time < best)
best = time;
outer = 1;
return best.count();
int main()
int j;
std::cout << "With aliasing ctor: " << test(std::shared_ptr<void>(), &j) << '\n'
<< "With empty deleter: " << test(&j, [] (auto) );
在我的机器上输出clang++ -march=native -O2
:
With aliasing ctor: 11812
With empty deleter: 651502
具有相同选项的 GCC 提供了更大的比率,5921:465794。
而带有-stdlib=libc++
的 Clang 产生了惊人的 12:613175。
【讨论】:
事实上,使用 libc++,shared_ptr 部分在使用别名 hack 时被完全优化掉:coliru.stacked-crooked.com/a/84b6d94a03d180b5 @JohannesSchaub-litb ...它发出的语言是什么? 它是转换为 x86 程序集之前的中间语言 (LLVM IR)。我发现更容易卡住,尤其是循环,因为它仍然是 SSA 形式。 区别在于第一个不分配新内存(它只存储一个指向已经存在的控制块的指针),而第二个每次创建共享指针时分配一个新的控制块。跨度> @IlyaPopov 不,默认构造一个 shared_ptr 不会分配任何东西。【参考方案2】:快速长凳与
#include <memory>
static void aliasConstructor(benchmark::State& state)
for (auto _ : state)
int j = 0;
std::shared_ptr<int> ptr(std::shared_ptr<void>(), &j);
benchmark::DoNotOptimize(ptr);
BENCHMARK(aliasConstructor);
static void NoOpDestructor(benchmark::State& state)
for (auto _ : state)
int j = 0;
std::shared_ptr<int> ptr(&j, [](int*));
benchmark::DoNotOptimize(ptr);
BENCHMARK(NoOpDestructor);
给予
a ratio of 1/30 for gcc 10.2 a ratio of 1/25 for clang 11 libc++所以别名构造函数获胜。
【讨论】:
以上是关于空别名 shared_ptr 是无操作删除 shared_ptr 的一个很好的替代方案吗?的主要内容,如果未能解决你的问题,请参考以下文章