Sometimes I need shared_ptr
instances that have a no-op deleter, because an API expects a shared_ptr
instance that it wants to store for a limited time but I am given a raw pointer that I am not allowed to own for a time larger than what I am running for.
For this case, I have been using a no-op deleter, such as [](const void *){}
, but today I found that there's another alternative to that, using (or abusing?) the aliasing constructor of 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);
}
My question is, what is the better way to do this and why? Are the performance expectations the same? With a no-op deleter I expect to pay some cost for the storage of the deleter and reference count, which doesn't appear to be the case when using the aliasing constructor with the empty shared_ptr
.
Concerning performance, the following benchmark shows erratic figures:
#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) {});
}
Output on my machine with clang++ -march=native -O2
:
With aliasing ctor: 11812
With empty deleter: 651502
GCC with identical options gives an even larger ratio, 5921:465794.
And Clang with -stdlib=libc++
yields a whopping 12:613175.
Quick bench with
#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);
gives
So alias constructor wins.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With