return 引用 std::unique_ptr 的原因

Reasons to return reference to std::unique_ptr

我想知道在 C++ 中通过引用 return 唯一指针是否有正当理由,即 std::unique_ptr<T>&?

我以前从未真正见过这种技巧,但我的新项目似乎经常使用这种模式。乍一看,它只是有效地打破/规避了“唯一所有权”契约,使得无法在编译时捕获错误。考虑以下示例:

class TmContainer {
public:
    TmContainer() {
        // Create some sort of complex object on heap and store unique_ptr to it
        m_time = std::unique_ptr<tm>(new tm());
        // Store something meaningful in its fields
        m_time->tm_year = 42;
    }

    std::unique_ptr<tm>& time() { return m_time; }

private:
    std::unique_ptr<tm> m_time;
};

auto one = new TmContainer();
auto& myTime = one->time();
std::cout << myTime->tm_year; // works, outputs 42
delete one;
std::cout << myTime->tm_year; // obviously fails at runtime, as `one` is deleted

请注意,如果我们 returned 只是 std::unique_ptr<tm>(不是引用),它会引发明显的编译时错误,或者会强制使用移动语义:

// Compile-time error
std::unique_ptr<tm> time() { return m_time; }

// Works as expected, but m_time is no longer owned by the container
std::unique_ptr<tm> time() { return std::move(m_time); }

我怀疑一般的经验法则是所有此类情况都需要使用 std::shared_ptr。我说得对吗?

根据设计,

std::unique_ptr 不满足 CopyConstructibleCopyAssignable 的要求。

因此,如果需要,必须将对象作为参考返回。

这条评论太长了。我对请求的用例没有好主意。我唯一想到的是实用程序库的一些中间件。

问题是你想要和需要建模什么。什么是语义。据我所知,返回引用没有任何用处。

您的示例中的唯一优点是没有显式析构函数。如果你想制作这段代码的精确镜像,那将是一个原始指针恕我直言。应该使用哪种确切类型取决于他们想要建模的确切语义。

也许您显示的代码背后的意图是 return 一个 non-owning 指针,它惯用地(据我所知)是通过原始指针建模的。如果您需要保证该对象还活着,那么您应该使用 shared_ptr 但请记住,这意味着共享所有权 - 即从 TmContianer.

中分离它的生命周期

使用原始指针会使您的代码同样失败,但有人可能会争辩说,也没有理由显式 delete,对象的生命周期可以通过作用域进行适当管理。

这当然是值得商榷的,因为单词和短语的语义和意义是有争议的,但我的经验表明,这就是 c++ 人们写作、谈论和理解指针的方式。

这有两个用例,我认为这表明设计不当。拥有 non-const 引用意味着您可以窃取资源或替换它,而无需提供单独的方法。

// Just create a handle to the managed object
auto& tm_ptr = tm_container.time();
do_something_to_tm(*tm_ptr);

// Steal the resource
std::unique_ptr<TmContainer> other_tm_ptr = std::move(tm_ptr);

// Replace the managed object with another one
tm_ptr = std::make_unique<TmContainer>;

我强烈反对这些做法,因为它们容易出错且可读性差。如果您确实需要此功能,最好提供如下所示的界面。

tm& time() { return *m_time; }

std::unique_ptr<tm> release_time() { return {std::move(m_time)}; }

// If tm is cheap to move
void set_time(tm t) { m_time = make_unique<tm>(std::move(t)); }

// If tm is not cheap to move or not moveable at all
void set_time(std::unique_ptr t_ptr) { m_time = std::move(t_ptr); }