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
不满足 CopyConstructible 或 CopyAssignable 的要求。
因此,如果需要,必须将对象作为参考返回。
这条评论太长了。我对请求的用例没有好主意。我唯一想到的是实用程序库的一些中间件。
问题是你想要和需要建模什么。什么是语义。据我所知,返回引用没有任何用处。
您的示例中的唯一优点是没有显式析构函数。如果你想制作这段代码的精确镜像,那将是一个原始指针恕我直言。应该使用哪种确切类型取决于他们想要建模的确切语义。
也许您显示的代码背后的意图是 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); }
我想知道在 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
不满足 CopyConstructible 或 CopyAssignable 的要求。
因此,如果需要,必须将对象作为参考返回。
这条评论太长了。我对请求的用例没有好主意。我唯一想到的是实用程序库的一些中间件。
问题是你想要和需要建模什么。什么是语义。据我所知,返回引用没有任何用处。
您的示例中的唯一优点是没有显式析构函数。如果你想制作这段代码的精确镜像,那将是一个原始指针恕我直言。应该使用哪种确切类型取决于他们想要建模的确切语义。
也许您显示的代码背后的意图是 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); }