是否可以实现 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 会被清除(但我很容易出错:标准中可能有一个条款明确要求:操作的其他语义并未暗示它) .