使用原始指针和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* carPtr{nullptr};
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!
我没有看到差异。在两个变种我基本上做同样的事情。我错过了什么?
答案 0 :(得分:3)
在第二个“变体”中,您将一个非空指针值分配给carPtr
:
//Variant 2:
std::unique_ptr<Car>car = std::make_unique<Car>();
carPtr = car.get();
car.reset();
这使得carPtr
不是空指针。
因此,输出表明它不是nullpointer。这在大多数系统中是可以预期的。但由于它正式未定义行为使用这个现在悬空的指针值,即使只是为了检查它是否为空而检查它,输出原则上可以是任何东西。你甚至可以获得可怕的红鼻窦守护效果。例如。
指针悬空,指的是没有对象,因为它所引用的对象已经通过car.reset()
调用被破坏。
为了为第一个变体创建漂亮的未定义行为,
//Variant 1:
func(carPtr);
...只需从
更改功能签名void func(Car* carPtr)
到
void func(Car*& carPtr)
通过引用传递参数,以便函数可以更改实际参数。
现在函数调用将carPtr
更改为悬空指针值,当您尝试检查它是否为空时,随后的UB。
答案 1 :(得分:2)
你的第一个变体与你的第二个没有任何共同之处。
在第一个变体中,您创建了一些完全独立的unique_ptr
(本地到func
函数),指向一些完全独立的Car
对象。然后你摧毁它们。函数内部的carPtr = car.get();
基本上是无操作,无法执行任何操作。因此,func
函数与main
中的任何内容完全分离,并且不会影响main
中的任何内容。
第二种变体非常不同。在main
中,您创建了一个指向unique_ptr
对象的Car
。然后你get()
指向该对象的指针,并将其存储在carPtr
的{{1}}中。这会更改main
中carPtr
的值,并更改程序的行为。