ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

c – 可以将shared_ptr向上转换为shared_ptr导致未定义的行为吗?

2019-07-22 11:05:21  阅读:292  来源: 互联网

标签:c shared-ptr pointers void-pointers


共享指针非常聪明.他们记住了最初构造的类型,以便正确删除它们.以此为例:

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; }.由于指针(在第一个示例中)在指向A的指针中保存B实例并正确删除它,因此必须以某种方式向下转发它.

我的问题是:以下代码片段是否调用未定义的行为?

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并不重要,那个中间结构不会改变.它不能改变,因为你的vptr和ptr虽然有不同的类型,但它们共享引用计数器(当然还有对象和删除器).

顺便说一下,中间结构是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,pointers,void-pointers
来源: https://codeday.me/bug/20190722/1502310.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有