返回unique_ptr变量将返回错误

时间:2019-09-13 03:03:15

标签: c++ pointers

我遇到了一个奇怪的问题。这样写时,我在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_也是如此),该错误被删除了。

我知道我可以用其他方式编写这种代码,但我特别好奇为什么在此实现中行为是这样的。

有人可以启发我吗?

2 个答案:

答案 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)或使用值语义来复制实际对象而不是指向它的指针。