我遇到了一个奇怪的问题。这样写时,我在setGetDog()方法中收到一条错误消息:
class Dog
{
public:
void bark() { std::cout << "Bark!" << std::endl; }
};
class test
{
public:
std::unique_ptr<Dog> setGetDog()
{
if (!dog_)
{
dog_ = std::make_unique<Dog>();
}
return dog_;
}
private:
std::unique_ptr<Dog> dog_;
};
int main()
{
auto testClass = std::make_unique<test>();
auto dog = testClass->setGetDog();
}
错误是这样的:
error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Dog; _Dp = std::default_delete<Dog>]'
但是当我将实现更改为:
std::unique_ptr<Dog> setGetDog()
{
return std::make_unique<Dog>();
}
它很好用。
dog_
有什么问题?
我不明白为什么该错误指出,实际上在调用该方法时test
仍然有效(因此dog_
也是如此),该错误被删除了。
我知道我可以用其他方式编写这种代码,但我特别好奇为什么在此实现中行为是这样的。
有人可以启发我吗?
答案 0 :(得分:3)
对象所具有的几种不同类型的值类别/状况会影响从函数返回值的方式。首先让我们看一下右值类别。当你做
std::unique_ptr<Dog> setGetDog()
{
return std::make_unique<Dog>();
}
std::make_unique<Dog>()
通过值返回,使返回对象成为右值。这意味着,当编译器返回对象时,它将隐式移动它。 (编译器也可以删除副本,但这对本次讨论无关紧要)由于对象已移动,因此std::unique_ptr
是可移动的,因此不会有任何问题。
第二种情况是当您有一个左值,但它是一个函数局部对象。如果你有
std::unique_ptr<Dog> setGetDog()
{
auto ptr = std::make_unique<Dog>();
return ptr;
}
这里ptr
是一个左值,但它已经消失了,因此我们可以将其称为xvalue(过期值)。使用xvalues时,返回它们时,编译器将尝试移动它,因为在这种情况下移动它是安全的操作。如果移动操作不存在/不可行,则编译器将退回到复制。由于std::unique_ptr
是可移动的,因此可以毫无错误地移动。
最后一种情况是,当您拥有普通的左值时
class test
{
public:
std::unique_ptr<Dog> setGetDog()
{
if (!dog_)
{
dog_ = std::make_unique<Dog>();
}
return dog_;
}
private:
std::unique_ptr<Dog> dog_;
};
在这里,dog_
是类的成员,而不是函数本地的对象。这意味着编译器唯一可以做的就是尝试进行复制。由于您无法复制unique_ptr
,因此会出现错误。您将需要return std::move(dog_);
来进行编译,但是如果这样做,该类中的dog_
将是空的。
答案 1 :(得分:1)
当您只返回unique_ptr
时,可以应用return value optimization。这是因为unique_ptr
实际上可以在调用函数中的unique_ptr实例的地址处构造。编译器允许这样做,因为没有复制发生。
添加if时,该函数可能会或可能不会尝试返回现有实例。这是运行时的决定。现有实例无法在调用函数所需的位置构造它,因此必须将其复制。
unique_ptr
的副本构造函数已删除,可以保留其合同的唯一部分,因此代码无法编译。
根据您的需要,您可能需要使用shared_ptr
,使用std::move
(可能是错误的,它会清空类内部的unique_ptr
)或使用值语义来复制实际对象而不是指向它的指针。