与虚拟析构函数相比,子类析构的运行时开销为 shared_ptr
Runtime overhead of shared_ptr for subclass destruction compared to virtual destructor
我在 Youtube 视频中发现了 shared_ptr
模仿虚拟析构函数行为的技巧 (https://www.youtube.com/watch?v=ZiNGWHg5Z-o&list=PLE28375D4AC946CC3&index=6), and upon searching the internet came across this SO answer: shared_ptr magic :)
通常情况下,如果B继承自A并有自己的析构函数,我们需要在基classA中有一个虚析构函数来保证B的析构函数被正确调用。但是,使用 shared_ptr
可以避免对虚拟析构函数的需求。
由于在多态函数中存在 vtable 查找的运行时开销,我很想知道 shared_ptr
技巧是否可以避免这种开销。
Shares ptr类型擦除析构函数;各种类型的擦除往往都有 "similar" 成本;给或取二的因数。有些比其他的保存缓存未命中或两次。一种类型擦除是虚函数表。
共享指针究竟如何擦除破坏留给实现。但是与内联函数调用相比,类型擦除从来都不是完全免费的。
很难证明哪个更有效,因为缓存耗尽可能比您尝试的任何微基准测试都更重要。在这两种情况下,它都不太可能成为减速的巨大来源。
首先,"trick" 可以用 unique_ptr
实现。您只需提供一个类型擦除删除器,如下所示。
但是,如果您查看实现,您当然会发现它涉及通过函数指针的调用 - 这正是虚拟析构函数在幕后所做的。
虚函数调用并不昂贵。它们只是涉及一次更多的内存获取。如果您在一个紧密的循环中执行此操作,这是唯一一次性能成为问题,那么该提取几乎肯定会被缓存。
此外,如果编译器能够证明它知道正确的析构函数,它将完全省略多态查找(当然,打开了优化)。
长话短说,如果这是您唯一的性能问题,那么您就没有性能问题。如果您对性能有顾虑,并且认为这是因为虚拟析构函数,那么恕我直言,您肯定是错误的。
示例代码:
#include <iostream>
#include <memory>
struct A {
~A() { std::cout << "~A\n"; }
};
struct B : A {
~B() { std::cout << "~B\n"; }
};
struct poly_deleter {
template<class T>
struct tag {
};
template<class T>
static void delete_it(void *p) { delete reinterpret_cast<T *>(p); }
template<class T>
poly_deleter(tag<T>) : deleter_(&delete_it<T>) {}
void operator()(void *p) const { deleter_(p); }
void (*deleter_)(void *) = nullptr;
};
template<class T> using unique_poly_ptr = std::unique_ptr<T, poly_deleter>;
template<class T, class...Args> auto make_unique_poly(Args&&...args) -> unique_poly_ptr<T>
{
return unique_poly_ptr<T> {
new T (std::forward<Args>(args)...),
poly_deleter(poly_deleter::tag<T>())
};
};
int main()
{
auto pb = make_unique_poly<B>();
auto pa = std::move(pb);
pa.reset();
}
预期输出:
~B
~A
我在 Youtube 视频中发现了 shared_ptr
模仿虚拟析构函数行为的技巧 (https://www.youtube.com/watch?v=ZiNGWHg5Z-o&list=PLE28375D4AC946CC3&index=6), and upon searching the internet came across this SO answer: shared_ptr magic :)
通常情况下,如果B继承自A并有自己的析构函数,我们需要在基classA中有一个虚析构函数来保证B的析构函数被正确调用。但是,使用 shared_ptr
可以避免对虚拟析构函数的需求。
由于在多态函数中存在 vtable 查找的运行时开销,我很想知道 shared_ptr
技巧是否可以避免这种开销。
Shares ptr类型擦除析构函数;各种类型的擦除往往都有 "similar" 成本;给或取二的因数。有些比其他的保存缓存未命中或两次。一种类型擦除是虚函数表。
共享指针究竟如何擦除破坏留给实现。但是与内联函数调用相比,类型擦除从来都不是完全免费的。
很难证明哪个更有效,因为缓存耗尽可能比您尝试的任何微基准测试都更重要。在这两种情况下,它都不太可能成为减速的巨大来源。
首先,"trick" 可以用 unique_ptr
实现。您只需提供一个类型擦除删除器,如下所示。
但是,如果您查看实现,您当然会发现它涉及通过函数指针的调用 - 这正是虚拟析构函数在幕后所做的。
虚函数调用并不昂贵。它们只是涉及一次更多的内存获取。如果您在一个紧密的循环中执行此操作,这是唯一一次性能成为问题,那么该提取几乎肯定会被缓存。
此外,如果编译器能够证明它知道正确的析构函数,它将完全省略多态查找(当然,打开了优化)。
长话短说,如果这是您唯一的性能问题,那么您就没有性能问题。如果您对性能有顾虑,并且认为这是因为虚拟析构函数,那么恕我直言,您肯定是错误的。
示例代码:
#include <iostream>
#include <memory>
struct A {
~A() { std::cout << "~A\n"; }
};
struct B : A {
~B() { std::cout << "~B\n"; }
};
struct poly_deleter {
template<class T>
struct tag {
};
template<class T>
static void delete_it(void *p) { delete reinterpret_cast<T *>(p); }
template<class T>
poly_deleter(tag<T>) : deleter_(&delete_it<T>) {}
void operator()(void *p) const { deleter_(p); }
void (*deleter_)(void *) = nullptr;
};
template<class T> using unique_poly_ptr = std::unique_ptr<T, poly_deleter>;
template<class T, class...Args> auto make_unique_poly(Args&&...args) -> unique_poly_ptr<T>
{
return unique_poly_ptr<T> {
new T (std::forward<Args>(args)...),
poly_deleter(poly_deleter::tag<T>())
};
};
int main()
{
auto pb = make_unique_poly<B>();
auto pa = std::move(pb);
pa.reset();
}
预期输出:
~B
~A