是否可以实现 std::move-and-clear 函数?
is it possible to implement a std::move-and-clear function?
是否可以编写一个函数 move_and_clear 这样,
对于任何 STL 容器:
do_something_with(move_and_clear(container));
相当于:
do_something_with(std::move(container));
container.clear();
?
这是我的第一次尝试,但没有成功。
我想我选对了类型
(虽然这个的生产版本可能会加入一些
std::remove_reference's), 编译成功,
但它失败或崩溃,因为它在超出范围后访问了 scratch。
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
这是我的第二次尝试。这确实有效,但它是一个预处理器宏,因此是邪恶的:
template<class T>
T &&move_and_clear_helper(T &t, T &&scratch)
{
std::swap(scratch, t);
return std::move(scratch);
}
#define move_and_clear(t) move_and_clear_helper(t, decltype(t)())
我的第三次尝试是另一个同样有效的宏,这次
使用 lambda 而不是命名的辅助函数。
所以它比以前的宏更独立一些,
但可能不太可读,当然它仍然是邪恶的,因为它是一个宏:
#define move_and_clear(t) \
[](decltype(t) &tparam, decltype(t) &&scratch){ \
std::swap(scratch, tparam); \
return std::move(scratch); \
}(t, decltype(t)())
这是一个结合了我的三个尝试的可编译程序:
/*
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear1 -DWHICH=1
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear2 -DWHICH=2
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear3 -DWHICH=3
./move_and_clear1 # assert-fails
./move_and_clear2 # succeeds
./move_and_clear3 # succeeds
*/
#include <assert.h>
#include <iostream>
#include <memory>
#include <vector>
#if WHICH == 1
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
#elif WHICH == 2
template<class T>
T &&move_and_clear_helper(T &t, T &&scratch)
{
std::swap(scratch, t);
return std::move(scratch);
}
#define move_and_clear(t) move_and_clear_helper(t, decltype(t)())
#elif WHICH == 3
#define move_and_clear(t) \
[](decltype(t) &tparam, decltype(t) &&scratch){ \
std::swap(scratch, tparam); \
return std::move(scratch); \
}(t, decltype(t)())
#endif
// Example "do_something_with":
// takes an rvalue reference to a vector that must have size 3,
// steals its contents, and leaves it in a valid but unspecified state.
// (Implementation detail: leaves it with 7 elements.)
template<typename T>
void plunder3_and_leave_in_unspecified_state(std::vector<T> &&v)
{
assert(v.size() == 3);
std::vector<T> pirate(7);
assert(pirate.size() == 7);
std::swap(pirate, v);
assert(pirate.size() == 3);
assert(v.size() == 7);
}
int main(int, char**)
{
{
std::cout << "Using std::move and clear ..." << std::endl << std::flush;
std::vector<std::unique_ptr<int>> v(3);
assert(v.size() == 3);
plunder3_and_leave_in_unspecified_state(std::move(v));
assert(v.size() == 7); // (uses knowledge of plunder's impl detail)
v.clear();
assert(v.empty());
std::cout << "done." << std::endl << std::flush;
}
{
std::cout << "Using move_and_clear ..." << std::endl << std::flush;
std::vector<std::unique_ptr<int>> v(3);
assert(v.size() == 3);
plunder3_and_leave_in_unspecified_state(move_and_clear(v));
assert(v.empty());
std::cout << "done." << std::endl << std::flush;
}
}
有没有办法在不使用宏的情况下将 move_and_clear 实现为模板函数?
std::move 定义为移动 内容,因此源容器将被有效清除。
(或者我误解了你的问题)
Is it possible to write a function move_and_clear such that, for any
STL container:
do_something_with(move_and_clear(container));
is equivalent to:
do_something_with(std::move(container));
container.clear();
?
template<typename T>
T move_and_clear(T& data){
T rtn(std::move(data));
data.clear();
return rtn;
}
return 值将在调用站点被视为 rvalue
。
同样,这将享受 Return 值优化(在任何健全的编译器中) 的好处。最肯定的是,内联。请参阅 Howard Hinnant's answer to this 问题。
同样,STL 容器具有移动构造函数,但对于任何其他自定义容器,最好将其限制为 move-constructible
类型。否则,你可以用一个不动的conatiner来调用它,并且你在那里有一个必要的副本。
template<typename T>
auto move_and_clear(T& data)
-> std::enable_if_t<std::is_move_constructible<T>::value, T>
{
T rtn(std::move(data));
data.clear();
return rtn;
}
参见:This answer
编辑:
如果您担心 RVO,我不知道任何主要 编译器不会在优化构建中执行 RVO(除非通过开关明确关闭) .还有一个 proposal to make it mandatory,希望我们应该在 C++17 中看到它。
编辑2:
该论文被纳入 C++17
的工作草案,参见 this
这是一个不需要额外移动容器但引入代理对象的实现:
template <class T>
class ClearAfterMove
{
T &&object;
public:
ClearAfterMove(T &&object) : object(std::move(object)) {}
ClearAfterMove(ClearAfterMove&&) = delete;
~ClearAfterMove() { object.clear(); }
operator T&& () const { return std::move(object); }
};
template <class T>
ClearAfterMove<T> move_and_clear(T &t)
{
return { std::move(t) };
}
这是如何工作的,它创建了一个不可移动的对象 ClearAfterMove
,它将包裹源容器 t
,并在 t
上调用 clear
(包装器)超出范围。
在这样的通话中:
do_something_with(move_and_clear(container));
将通过调用 move_and_clear
创建一个临时 ClearAfterMove
对象(我们称之为 cam
)包装 container
。然后,这个临时值将被其转换运算符转换为右值引用,并传递给 do_something_with
.
临时变量在创建它们的完整表达式末尾超出范围。对于 cam
,这意味着一旦对 do_something_with
的调用被解析,它将被销毁,这正是您想要的。
请注意,这具有不产生任何额外移动的优点,以及所有代理对象解决方案都具有的缺点:它不能很好地进行类型推导,例如:
auto x = move_and_clear(y);
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
这很接近。但是你打字太多了:
template<class T>
T move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return scratch;
}
scratch
将被 return 值省略(除非有人设置恶意编译器标志)。
略有改善:
template<class T>
T move_and_clear(T &t)
{
T scratch;
using std::swap;
swap(scratch, t);
return scratch;
}
在 swap
上启用 Koenig 查找,这意味着如果有人在他们的容器上编写了一个比 std::swap
更有效的免费 swap
操作,它将被调用。
现在,实际上,std
容器的成功移动将清除它。唯一不清除源容器甚至有点意义的是 std::basic_string
当 SSO(小字符串优化)处于活动状态时,清除所需的工作量是如此之小,我看不到库编写器不是为了理智而这样做。
但是,这段带有交换的代码保证源对象将具有类型为 T
的空对象的状态。
请注意,这 不适用于 所有可能的 C++ 实现:
template<class T>
T move_and_clear(T &t)
{
T scratch = std::move(t);
return scratch;
}
对于大多数std
容器来说,对move-construction的要求比较严格,iterators必须传递。 Iterators transfering意味着它在实践中必须清除源。
在 std::basic_string
上,由于小字符串优化,迭代器不必传输。我知道不能保证源 basic_string
会被清除(但我很容易出错:标准中可能有一个条款明确要求:操作的其他语义并未暗示它) .
是否可以编写一个函数 move_and_clear 这样, 对于任何 STL 容器:
do_something_with(move_and_clear(container));
相当于:
do_something_with(std::move(container));
container.clear();
?
这是我的第一次尝试,但没有成功。 我想我选对了类型 (虽然这个的生产版本可能会加入一些 std::remove_reference's), 编译成功, 但它失败或崩溃,因为它在超出范围后访问了 scratch。
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
这是我的第二次尝试。这确实有效,但它是一个预处理器宏,因此是邪恶的:
template<class T>
T &&move_and_clear_helper(T &t, T &&scratch)
{
std::swap(scratch, t);
return std::move(scratch);
}
#define move_and_clear(t) move_and_clear_helper(t, decltype(t)())
我的第三次尝试是另一个同样有效的宏,这次 使用 lambda 而不是命名的辅助函数。 所以它比以前的宏更独立一些, 但可能不太可读,当然它仍然是邪恶的,因为它是一个宏:
#define move_and_clear(t) \
[](decltype(t) &tparam, decltype(t) &&scratch){ \
std::swap(scratch, tparam); \
return std::move(scratch); \
}(t, decltype(t)())
这是一个结合了我的三个尝试的可编译程序:
/*
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear1 -DWHICH=1
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear2 -DWHICH=2
g++ --std=c++11 -W -Wall -g move_and_clear.cc -o move_and_clear3 -DWHICH=3
./move_and_clear1 # assert-fails
./move_and_clear2 # succeeds
./move_and_clear3 # succeeds
*/
#include <assert.h>
#include <iostream>
#include <memory>
#include <vector>
#if WHICH == 1
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
#elif WHICH == 2
template<class T>
T &&move_and_clear_helper(T &t, T &&scratch)
{
std::swap(scratch, t);
return std::move(scratch);
}
#define move_and_clear(t) move_and_clear_helper(t, decltype(t)())
#elif WHICH == 3
#define move_and_clear(t) \
[](decltype(t) &tparam, decltype(t) &&scratch){ \
std::swap(scratch, tparam); \
return std::move(scratch); \
}(t, decltype(t)())
#endif
// Example "do_something_with":
// takes an rvalue reference to a vector that must have size 3,
// steals its contents, and leaves it in a valid but unspecified state.
// (Implementation detail: leaves it with 7 elements.)
template<typename T>
void plunder3_and_leave_in_unspecified_state(std::vector<T> &&v)
{
assert(v.size() == 3);
std::vector<T> pirate(7);
assert(pirate.size() == 7);
std::swap(pirate, v);
assert(pirate.size() == 3);
assert(v.size() == 7);
}
int main(int, char**)
{
{
std::cout << "Using std::move and clear ..." << std::endl << std::flush;
std::vector<std::unique_ptr<int>> v(3);
assert(v.size() == 3);
plunder3_and_leave_in_unspecified_state(std::move(v));
assert(v.size() == 7); // (uses knowledge of plunder's impl detail)
v.clear();
assert(v.empty());
std::cout << "done." << std::endl << std::flush;
}
{
std::cout << "Using move_and_clear ..." << std::endl << std::flush;
std::vector<std::unique_ptr<int>> v(3);
assert(v.size() == 3);
plunder3_and_leave_in_unspecified_state(move_and_clear(v));
assert(v.empty());
std::cout << "done." << std::endl << std::flush;
}
}
有没有办法在不使用宏的情况下将 move_and_clear 实现为模板函数?
std::move 定义为移动 内容,因此源容器将被有效清除。
(或者我误解了你的问题)
Is it possible to write a function move_and_clear such that, for any STL container:
do_something_with(move_and_clear(container));
is equivalent to:
do_something_with(std::move(container)); container.clear();
?
template<typename T>
T move_and_clear(T& data){
T rtn(std::move(data));
data.clear();
return rtn;
}
return 值将在调用站点被视为 rvalue
。
同样,这将享受 Return 值优化(在任何健全的编译器中) 的好处。最肯定的是,内联。请参阅 Howard Hinnant's answer to this 问题。
同样,STL 容器具有移动构造函数,但对于任何其他自定义容器,最好将其限制为 move-constructible
类型。否则,你可以用一个不动的conatiner来调用它,并且你在那里有一个必要的副本。
template<typename T>
auto move_and_clear(T& data)
-> std::enable_if_t<std::is_move_constructible<T>::value, T>
{
T rtn(std::move(data));
data.clear();
return rtn;
}
参见:This answer
编辑:
如果您担心 RVO,我不知道任何主要 编译器不会在优化构建中执行 RVO(除非通过开关明确关闭) .还有一个 proposal to make it mandatory,希望我们应该在 C++17 中看到它。
编辑2:
该论文被纳入 C++17
的工作草案,参见 this
这是一个不需要额外移动容器但引入代理对象的实现:
template <class T>
class ClearAfterMove
{
T &&object;
public:
ClearAfterMove(T &&object) : object(std::move(object)) {}
ClearAfterMove(ClearAfterMove&&) = delete;
~ClearAfterMove() { object.clear(); }
operator T&& () const { return std::move(object); }
};
template <class T>
ClearAfterMove<T> move_and_clear(T &t)
{
return { std::move(t) };
}
这是如何工作的,它创建了一个不可移动的对象 ClearAfterMove
,它将包裹源容器 t
,并在 t
上调用 clear
(包装器)超出范围。
在这样的通话中:
do_something_with(move_and_clear(container));
将通过调用 move_and_clear
创建一个临时 ClearAfterMove
对象(我们称之为 cam
)包装 container
。然后,这个临时值将被其转换运算符转换为右值引用,并传递给 do_something_with
.
临时变量在创建它们的完整表达式末尾超出范围。对于 cam
,这意味着一旦对 do_something_with
的调用被解析,它将被销毁,这正是您想要的。
请注意,这具有不产生任何额外移动的优点,以及所有代理对象解决方案都具有的缺点:它不能很好地进行类型推导,例如:
auto x = move_and_clear(y);
template<class T>
T &&move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return std::move(scratch);
}
这很接近。但是你打字太多了:
template<class T>
T move_and_clear(T &t)
{
T scratch;
std::swap(scratch, t);
return scratch;
}
scratch
将被 return 值省略(除非有人设置恶意编译器标志)。
略有改善:
template<class T>
T move_and_clear(T &t)
{
T scratch;
using std::swap;
swap(scratch, t);
return scratch;
}
在 swap
上启用 Koenig 查找,这意味着如果有人在他们的容器上编写了一个比 std::swap
更有效的免费 swap
操作,它将被调用。
现在,实际上,std
容器的成功移动将清除它。唯一不清除源容器甚至有点意义的是 std::basic_string
当 SSO(小字符串优化)处于活动状态时,清除所需的工作量是如此之小,我看不到库编写器不是为了理智而这样做。
但是,这段带有交换的代码保证源对象将具有类型为 T
的空对象的状态。
请注意,这 不适用于 所有可能的 C++ 实现:
template<class T>
T move_and_clear(T &t)
{
T scratch = std::move(t);
return scratch;
}
对于大多数std
容器来说,对move-construction的要求比较严格,iterators必须传递。 Iterators transfering意味着它在实践中必须清除源。
在 std::basic_string
上,由于小字符串优化,迭代器不必传输。我知道不能保证源 basic_string
会被清除(但我很容易出错:标准中可能有一个条款明确要求:操作的其他语义并未暗示它) .