可能失败的 unique_ptr 移动的函数签名?
Function signature for potentially failed unique_ptr move?
假设我正在编写一个接受 unique_ptr
的 enqueue()
函数,但我只想在入队 returns 成功时声明其所有权。如果队列已满,我想保持 unique_ptr
不变(用户可以稍后使用相同的项目重试)
bool enqueue(std::unique_ptr&& item){
if(!vector.full()){
vector.emplace(std::move(item));
return true;
}
return false;
}
// usage
auto item_ptr = make_unique<>();
while(!enqueue(std::move(item_ptr))){
// item_ptr is not moved
}
我也可以定义函数来取而代之的是左值引用
bool enqueue(std::unique_ptr& item)
while(!enqueue(item_ptr)){
// item_ptr is not moved
}
我不确定该选哪个,它们似乎都有点反模式,因为通常 std::move
表示删除 unique_ptr(大多数时候,我使用的函数是unique_ptr
按值计算),也许有更好的解决方案?
您可以从您的函数中 return unique_ptr。如果失败了return原来的。如果成功 return 一个空 unique_ptr.
那么你可以这样称呼它:
#include <iostream>
#include <memory>
#include <vector>
int counter = 10;
template <typename T> std::unique_ptr<T> enqueue(std::unique_ptr<T> p) {
if (--counter == 0)
return std::unique_ptr<T>();
return p;
}
int main() {
auto item_ptr = std::make_unique<int>(8);
while (item_ptr = enqueue(std::move(item_ptr))) {
std::cout << "looping\n";
}
std::cout << "moved\n";
return 0;
}
其实,我最好去试试这个。是的,我的第一个想法有一个错误。从 while 循环中删除 !
。总是测试。 :-)
具有 RValue 引用的第一个版本接受一个临时的。
因此,如果移动没有发生,您将在临时 std::unique_ptr
中拥有一个实例,该实例将很快被删除。
这就是让我不确定它是否是一个好的选择(或令人惊讶的效果)的原因。
起码,大家应该知道这一点。
关于问题
what happens if you do while (!enqueue(make_unique<>()))...
我做了一个 MCVE(只是为了确定):
#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Test()\n"; }
~Test() { std::cout << "Test::~Test()\n"; }
};
bool enqueue(std::unique_ptr<Test>&& item)
{
return false;
}
int main()
{
for (int i = 0; i < 3 && !enqueue(std::make_unique<Test>()); ++i);
}
输出:
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
如果它旨在防止“滥用”并确保不为临时调用 enqueue()
,则可以删除 RValue 版本。
关于 std::move
我喜欢的心态是,只有我表示我可以接受正在修改的对象。这不是导致删除,而是允许可能破坏性的事情发生。
我觉得你的第一个选项没问题。调用者必须通过用 std::move
标记其左值来明确授予权限,并且他们可以通过检查函数的 return 值来检查是否确实发生了某些事情。所以他们不会让他们关心的对象突然消失。这很好 够了 IMO.
但是就像说的,这个版本也提供了传递临时unique_ptr
的选项。如果那种情况下毫无意义的工作让您感到困扰,那么我们或许应该换一种方法。
我有时喜欢从 Google 样式指南中获取的一条准则是 [input/]output 参数由非拥有原始指针传递。这基本上将它们限制为左值,同时保留允许修改它们所需的明确性。所以你可以这样做:
bool enqueue(std::unique_ptr<T>* item){
if(!vector.full()){
vector.emplace(std::move(*item));
return true;
}
return false;
}
// usage
auto item_ptr = make_unique<T>();
while(!enqueue(&item_ptr)){
// item_ptr is not modified
}
在这样的风格指南下,寻址运算符 &
的作用与 std::move
相同。这是我们告诉功能:“继续,如果需要修改它。”
这个解决方案对我来说似乎有点棘手,因为你正在通过 unique_ptr
,但它可能会或可能不会被声明,因此需要以某种方式处理这种行为:比如,返回成功时为空 unique_ptr
或 error/success 代码,进行冗余或难以理解的检查等。我认为你可以做的是在 full() 方法或类似方法的帮助下检查你的队列是否有外部空闲 space。那么您的代码将如下所示:
if(!full()) {
enqueue(std::move(...));
}
假设我正在编写一个接受 unique_ptr
的 enqueue()
函数,但我只想在入队 returns 成功时声明其所有权。如果队列已满,我想保持 unique_ptr
不变(用户可以稍后使用相同的项目重试)
bool enqueue(std::unique_ptr&& item){
if(!vector.full()){
vector.emplace(std::move(item));
return true;
}
return false;
}
// usage
auto item_ptr = make_unique<>();
while(!enqueue(std::move(item_ptr))){
// item_ptr is not moved
}
我也可以定义函数来取而代之的是左值引用
bool enqueue(std::unique_ptr& item)
while(!enqueue(item_ptr)){
// item_ptr is not moved
}
我不确定该选哪个,它们似乎都有点反模式,因为通常 std::move
表示删除 unique_ptr(大多数时候,我使用的函数是unique_ptr
按值计算),也许有更好的解决方案?
您可以从您的函数中 return unique_ptr。如果失败了return原来的。如果成功 return 一个空 unique_ptr.
那么你可以这样称呼它:
#include <iostream>
#include <memory>
#include <vector>
int counter = 10;
template <typename T> std::unique_ptr<T> enqueue(std::unique_ptr<T> p) {
if (--counter == 0)
return std::unique_ptr<T>();
return p;
}
int main() {
auto item_ptr = std::make_unique<int>(8);
while (item_ptr = enqueue(std::move(item_ptr))) {
std::cout << "looping\n";
}
std::cout << "moved\n";
return 0;
}
其实,我最好去试试这个。是的,我的第一个想法有一个错误。从 while 循环中删除 !
。总是测试。 :-)
具有 RValue 引用的第一个版本接受一个临时的。
因此,如果移动没有发生,您将在临时 std::unique_ptr
中拥有一个实例,该实例将很快被删除。
这就是让我不确定它是否是一个好的选择(或令人惊讶的效果)的原因。
起码,大家应该知道这一点。
关于问题
what happens if you do while (!enqueue(make_unique<>()))...
我做了一个 MCVE(只是为了确定):
#include <memory>
#include <iostream>
struct Test {
Test() { std::cout << "Test::Test()\n"; }
~Test() { std::cout << "Test::~Test()\n"; }
};
bool enqueue(std::unique_ptr<Test>&& item)
{
return false;
}
int main()
{
for (int i = 0; i < 3 && !enqueue(std::make_unique<Test>()); ++i);
}
输出:
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
Test::Test()
Test::~Test()
如果它旨在防止“滥用”并确保不为临时调用 enqueue()
,则可以删除 RValue 版本。
关于 std::move
我喜欢的心态是,只有我表示我可以接受正在修改的对象。这不是导致删除,而是允许可能破坏性的事情发生。
我觉得你的第一个选项没问题。调用者必须通过用 std::move
标记其左值来明确授予权限,并且他们可以通过检查函数的 return 值来检查是否确实发生了某些事情。所以他们不会让他们关心的对象突然消失。这很好 够了 IMO.
但是就像unique_ptr
的选项。如果那种情况下毫无意义的工作让您感到困扰,那么我们或许应该换一种方法。
我有时喜欢从 Google 样式指南中获取的一条准则是 [input/]output 参数由非拥有原始指针传递。这基本上将它们限制为左值,同时保留允许修改它们所需的明确性。所以你可以这样做:
bool enqueue(std::unique_ptr<T>* item){
if(!vector.full()){
vector.emplace(std::move(*item));
return true;
}
return false;
}
// usage
auto item_ptr = make_unique<T>();
while(!enqueue(&item_ptr)){
// item_ptr is not modified
}
在这样的风格指南下,寻址运算符 &
的作用与 std::move
相同。这是我们告诉功能:“继续,如果需要修改它。”
这个解决方案对我来说似乎有点棘手,因为你正在通过 unique_ptr
,但它可能会或可能不会被声明,因此需要以某种方式处理这种行为:比如,返回成功时为空 unique_ptr
或 error/success 代码,进行冗余或难以理解的检查等。我认为你可以做的是在 full() 方法或类似方法的帮助下检查你的队列是否有外部空闲 space。那么您的代码将如下所示:
if(!full()) {
enqueue(std::move(...));
}