共享指针非常聪明。他们记住他们第一次构造时使用的类型,以便正确删除它们。以此为例:

struct A { virtual void test() = 0; };
struct B : A { void test() override {} };

void someFunc() {
    std::shared_ptr<A> ptr1;

    ptr1 = std::make_shared<B>();

    // Here at the end of the scope, B is deleted correctly
}

但是,void 指针似乎存在一个问题:要使 void 指针的向下转换有效,必须将其向下转换为最初向上转换的类型。

例如:
void* myB = new B;

// Okay, well defined
doStuff(static_cast<B*>(myB));

// uh oh, not good!
// For the same instance of a child object, a pointer to the base and
// a pointer to the child can be differrent.
doStuff(static_cast<A*>(myB));

使用 std::shared_ptr 时,当您使用 std::make_shared 时,删除器必须类似于此函数: [](B* ptr){ delete ptr; } 。由于指针(在第一个示例中)在指向 B 的指针中保存 A 实例并正确删除它,因此它必须以某种方式向下转换它。

我的问题是:以下代码片段是否调用了未定义的行为?
void someFunc() {
    {
        std::shared_ptr<void> ptr = std::make_shared<B>();

        // Deleting the pointer seems okay to me,
        // the shared_ptr knows that a B was originally allocated with a B and
        // will send the void pointer to the deleter that's delete a B.
    }

    std::shared_ptr<void> vptr;

    {
        std::shared_ptr<A> ptr = std::make_shared<B>();

        // ptr is pointing to the base, which can be
        // different address than the pointer to the child.

        // assigning the pointer to the base to the void pointer.
        // according to my current knowledge of void pointers,
        // any future use of the pointer must cast it to a A* or end up in UB.
        vptr = ptr;
    }

    // is the pointer deleted correctly or it tries to
    // cast the void pointer to a B pointer without downcasting to a A* first?
    // Or is it well defined and std::shared_ptr uses some dark magic unknown to me?
}

最佳答案

代码是正确的。
std::shared_ptr 在内部保存真正的指针和真正的删除器,就像它们在构造函数中一样,所以无论你如何向下转换它,只要向下转换有效,删除器就是正确的。
shared_ptr 实际上并不保存指向对象的指针,而是指向保存实际对象、引用计数器和删除器的中间结构的指针。如果您转换 shared_ptr 并不重要,中间结构不会改变。它无法更改,因为您的 vptrptr 虽然类型不同,但共享引用计数器(当然还有对象和删除器)。

顺便说一句,中间结构是 make_shared 优化的原因:它将中间结构和对象本身分配在同一个内存块中,并避免了额外的分配。

为了说明智能指针的作用,我编写了一个程序,其中包含由于您的问题而崩溃的普通指针(使用 GCC 6.2.1):

#include <memory>
#include <iostream>

struct A
{
    int a;
    A() :a(1) {}
    ~A()
    {
        std::cout << "~A " << a << std::endl;
    }
};

struct X
{
    int x;
    X() :x(3) {}
    ~X()
    {
        std::cout << "~X " << x << std::endl;
    }
};

struct B : X, A
{
    int b;
    B() : b(2) {}
    ~B()
    {
        std::cout << "~B " << b << std::endl;
    }
};

int main()
{
    A* a = new B;
    void * v = a;
    delete (B*)v; //crash!

    return 0;
}

实际上它打印了错误的整数值,这证明了 UB。
~B 0
~A 2
~X 1
*** Error in `./a.out': free(): invalid pointer: 0x0000000001629c24 ***

但是带有智能指针的版本工作得很好:
int main()
{
    std::shared_ptr<void> vptr;

    {
        std::shared_ptr<A> ptr = std::make_shared<B>();
        vptr = ptr;
    }
    return 0;
}

它按预期打印:
~B 2
~A 1
~X 3

关于c++ - 将 shared_ptr<T> 向上转换为 shared_ptr<void> 会导致未定义的行为吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/39624080/

10-11 03:54
查看更多