0

假设我有一个简单的类层次结构,如下所示,带有一个通用 api:

#include <memory>

class Base {
    public:
        void api() {
            foo();
        }

    protected:
        virtual void foo() {
            std::cout << "Base" << std::endl;

        }
    };

    class FirstLevel : public Base {
    protected:
        virtual void foo() {
            std::cout << "FirstLevel" << std::endl;
        }
    };

当我使用基类指针时,我得到正确的调度如下:

std::shared_ptr<Base> b = std::make_shared<Base>();
std::shared_ptr<Base> fl = std::make_shared<FirstLevel>();

b->api();
fl->api();

哪个正确打印:

Base
FirstLevel

但是,当我使用基类引用时,行为出乎意料:

Base &b_ref = *std::make_shared<Base>();
Base &fl_ref = *std::make_shared<FirstLevel>();

b_ref.api();
fl_ref.api();

打印:

FirstLevel
FirstLevel

为什么使用引用而不是指针时调度不同?

4

3 回答 3

3

您有未定义的行为,因为引用在您使用它们调用时悬空api()。在用于初始化b_ref和的行之后,由共享指针管理的对象不再存在fl_ref

您可以通过引用仍然存在的对象来修复它:

auto b = std::make_shared<Base>();
auto fl = std::make_shared<FirstLevel>();

Base &b_ref = *b;
Base &fl_ref = *fl;
于 2018-09-25T10:47:52.757 回答
2

最后一个示例中的返回值std::make_shared没有绑定到右值 ( std::shared_ptr<...>&&) 或const限定的左值引用 ( const std::shared_ptr<...>&),因此它的生命周期不会延长。相反,临时std::shared_ptr::operator*实例的返回值绑定到表达式 ( , )的左侧,这会导致未定义的行为。b_refl_ref

如果您想通过对 and 的非左值引用访问虚拟方法api()const您可以通过以下方式解决此问题BaseFirstLevel

auto b = std::make_shared<Base>();
Base& b_ref = *b;

b_ref.api();

和类似的FirstLevel。但是,不要在超出范围b_ref后使用。b您可以通过以下方式延长寿命

auto&& b = std::make_shared<Base>();
Base& b_ref = *b;

b_ref.api();

尽管这几乎与上述相同。

于 2018-09-25T10:49:32.633 回答
0

使智能指针(或任何欠对象)成为临时对象是不好的设计。

该设计问题会导致糟糕的生命周期管理,特别是破坏仍在使用的对象。这会导致未定义的行为;未定义的行为未定义,甚至不受标准的限制(它可以受其他原则、工具、设备的限制)。

在许多情况下,我们仍然可以尝试了解使用 UB 的代码在实践中是如何翻译的。您观察到的具体行为:

打印:

FirstLevel
FirstLevel

肯定是由于将销毁对象留下的内存解释为活对象引起的;因为当时没有重用该内存(由于偶然性,并且对程序或实现的任何更改都可能破坏该属性),您会看到一个对象处于销毁期间的状态。

在析构函数中,被析构对象的虚函数调用总是解析为析构函数类中函数的覆盖器:在内部Base::~Base,对的调用foo()解析为Base::foo()Base使用 vptr 和 vtables 的编译器(实际上,所有编译器)通过在基类析构函数执行开始时将 vptr 重置为 vtable for 来确保以这种方式解析虚拟调用。

所以你看到的是 vptr 仍然指向基类 vtable。

当然,调试实现有权在基类的析构函数末尾将 vptr 设置为其他值,以确保尝试在被破坏的对象上调用虚函数以清晰明确的方式失败。

于 2018-09-27T05:27:57.177 回答