与虚拟析构函数相比,shared_ptr用于子类销毁的运行时开销

时间:2017-04-25 11:40:59

标签: c++ performance oop c++11 shared-ptr

我在Youtube视频(https://www.youtube.com/watch?v=ZiNGWHg5Z-o&list=PLE28375D4AC946CC3&index=6)上模仿了虚拟析构函数的行为shared_ptr,并在搜索互联网时遇到了这样的答案:shared_ptr magic :)

通常,如果B继承自A并且有自己的析构函数,我们需要在基类A中使用虚拟析构函数来确保正确调用B的析构函数。但是,使用shared_ptr可以避免使用虚拟析构函数。

由于在多态函数中存在vtable查找的运行时开销,我很想知道shared_ptr技巧是否可以避免这种开销。

2 个答案:

答案 0 :(得分:3)

首先,"技巧"可以使用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

答案 1 :(得分:1)

股票ptr类型删除析构函数;各种类型的擦除往往都有“相似”的成本;给予或取两倍。有些节省了高速缓存未命中或其他两个。一种类型擦除是虚函数表。

共享ptr如何消除破坏将留给实现。但是与内联函数调用相比,类型擦除永远不会完全免费。

很难证明哪个更有效,因为缓存耗尽可能比您尝试的任何微基准更重要。在这两种情况下,它都不太可能成为减速的重要来源。