std::unique_ptr::get() 的奇怪返回行为

Posted

技术标签:

【中文标题】std::unique_ptr::get() 的奇怪返回行为【英文标题】:Odd return behaviour of std::unique_ptr::get() 【发布时间】:2016-03-14 19:06:58 【问题描述】:

我在使用原始指针和 std::unique_ptr.get() 时遇到了一些奇怪的行为。 给出这个例子:

#include <iostream>
class Car
public: 
    Car()std::cout << "car gets created\n"; 
    ~Car()std::cout << "car gets destroyed\n"; 
; 

void func(Car* carPtr)
    std::unique_ptr<Car> car = std::make_unique<Car>();
    carPtr = car.get();


int main()

    Car* carPtrnullptr;
    std::cout << "first check: \n";
    if(carPtr)
        std::cout << "car Pointer is NOT assigned to nullptr!\n";
    
    else
        std::cout << "car Pointer is assigned to nullptr\n";
    

//Variant 1: 
//func(carPtr);

//Variant 2: 
//std::unique_ptr<Car>car = std::make_unique<Car>();
//carPtr = car.get();
//car.reset();

    std::cout << "\nsecond check: \n";
    if(carPtr)
        std::cout << "car Pointer is NOT assigned to nullptr!\n";
    
    else
        std::cout << "car Pointer is assigned to nullptr\n";
    
    return 0;

Variant 1 和 Variant 2 做的事情基本相同:unique_ptr 将对象的地址返回给指针,然后对象被删除。但是,由于某种我不明白的原因,第二次检查中的输出有所不同。

变体 1 中的输出为:

first check: 
car Pointer is assigned to nullptr 
car gets created 
car gets destroyed

second check: 
car Pointer is assigned to nullptr

变体 2 中的输出为:

first check: 
car Pointer is assigned to nullptr 
car gets created 
car gets destroyed

second check: 
car Pointer is NOT assigned to nullptr!

我看不出有什么不同。在这两种变体中,我基本上都在做同样的事情。我错过了什么?

【问题讨论】:

func 的意义何在?它不会通过引用来获取它的参数,因此赋值carPtr = ... 基本上是没有意义的,因为局部carPtr 变量会随着它超出范围而消失。 【参考方案1】:

在您的第二个“变体”中,您将非空指针值分配给 carPtr

//Variant 2: 
std::unique_ptr<Car>car = std::make_unique<Car>();
carPtr = car.get();
car.reset();

这使得carPtr 不是空指针。

因此,输出表明它不是空指针。这在大多数系统上都是可以预料的。但是由于使用这个现在悬空的指针值是正式的未定义行为,即使只是为了检查它是否为空而检查它,输出原则上可以是任何东西。你甚至可以获得可怕的红鼻守护效果。例如。


指针悬空,没有引用任何对象,因为它所引用的对象已通过car.reset() 调用销毁。


为了也为第一个变体创建良好的未定义行为,

//Variant 1: 
func(carPtr);

...只需更改函数签名

void func(Car* carPtr)

void func(Car*& carPtr)

它通过引用传递参数,以便函数可以更改实际参数。

现在函数调用将carPtr 更改为一个悬空指针值,当您尝试检查它是否为空时,随之而来的是UB。

【讨论】:

所以这基本上只是基于未定义行为的巧合,两种变体都打印不同的输出? 不,不是巧合。这里的 UB 是“弱”的,因为它不太可能在现代系统上体现出来。这是正式的事情。但是空值检查可能会在某些分段架构上产生硬件异常。您看到的输出的原因也如答案中所述。 @Grundkurs:不是真的。您可以更改代码以摆脱未定义的行为,但变体之间的根本区别仍然存在。根本区别在于第一个变体main修改carPtr,而第二个变体确实main修改carPtr。 UB 不是重点。 现在我明白了,func-method 中的 carPtr 参数只是函数内的一个本地副本,main() 中的原始“carPtr”永远不会受到函数中分配的影响。但是检查指向托管对象的原始指针似乎是有问题的,因为它不能保证该对象仍然有效,因此检查可能会导致 UB。【参考方案2】:

您的第一个变体与您的第二个变体没有任何共同之处。

在第一个变体中,您创建了一些完全独立的unique_ptr(局部于func 函数),指向一些完全独立的Car 对象。然后你摧毁他们两个。您在函数中的carPtr = car.get(); 基本上是一个不执行任何操作的无操作。因此,您的 func 函数与 main 中的任何内容完全分离,并且不会影响 main 中的任何内容。

第二个变体非常不同。在main 中,您创建了一个指向Car 对象的unique_ptr。然后你 get() 指向该对象的指针并将其存储在来自 maincarPtr 中。这会改变maincarPtr 的值并改变程序的行为。

【讨论】:

以上是关于std::unique_ptr::get() 的奇怪返回行为的主要内容,如果未能解决你的问题,请参考以下文章

源码解析中看到的奇淫巧技

Swagger使用的奇淫技巧

bitParity - 查找整数中的奇数位

打印整数二进制的奇数位和偶数位

实用工具快上车,程序狗好用的奇淫技巧

你不知道的 docker 命令的奇淫怪巧