如何在使用基于范围的 for 循环遍历 std::vector of std::unique_ptr 时获取对象的所有权?

How to take ownership of an object while looping over std::vector of std::unique_ptr using a range based for loop?

我有一个 std::vector<std::unique_ptr<Kind>>,我想在迭代时清理它,而不显式调用其成员的析构函数 (.reset())。

Kind 是一个沉重的结构,它的大小在迭代过程中会增加。下一个对象不需要知道以前的对象,所以我想在不需要的时候清理一个 iterand。

我知道 vector 最终会清理干净,但到那时,很多 Kind 及其动态分配的内存加起来。我正在尝试将峰值内存减少到一个元素。

我想避免 reset 因为其他开发人员可能不知道动态分配,忘记在循环结束时调用重置并导致内存损失。

我无法创建副本,

for(std::unique_ptr<Kind> t : store)

我动不了

for(std::unique_ptr<Kind> &&t : store)

那我该怎么做呢?

#include <iostream>
#include <vector>

struct Kind{
    char a;
    char *array;
    Kind(const char c): a(c)
    {
    }
    ~Kind(){
      free(array); // internal custom deallocator.
    }
};

int main() {
    std::vector<std::unique_ptr<Kind>> store;
    store.push_back(std::make_unique<Kind>('y'));
    store.push_back(std::make_unique<Kind>('z'));

    for(std::unique_ptr<Kind> &t : store){
        // increase size of Kind.array.
        std::cout << t->a;
        // Use the Kind.array
        // clean up t automatically.
    }
    return 0;

}

将元素移出向量的示例。

int main() {
    std::vector<std::unique_ptr<Kind>> store;
    store.push_back(std::make_unique<Kind>('y'));
    for(std::unique_ptr<Kind> &t : store){
        auto tmp = std::move(t); // leaving a valid but empty entry in store
        std::cout << tmp->a;
        // clean up t automatically.
        // tmp runs out of scope and cleans up
    }
    return 0;
}

实际上与重置没有太大区别,但可能与您在实际程序中实际执行的操作相关。

How to take ownership of an object while looping over std::vector of std::unique_ptr using a range based for loop?

循环引用元素,std::move 将唯一指针指向另一个元素。示例:

for(std::unique_ptr<Kind> &t : store){
    std::unique_ptr<Kind> owner = std::move(t);
    // do something with newly owned pointer

I want to clean up

there's no need to keep older structs around

您可以通过重置指针来解除分配对象:

for(std::unique_ptr<Kind> &t : store) {
    // do something
    t.reset();

也就是说,这通常是不必要的。当向量超出范围时,它们将被自动销毁。

I'm trying to save some memory here

如果您在迭代时分配动态对象,这可能会有用。否则不会影响峰值内存使用。

如果你想确保在每次迭代后立即删除实例并且你不能等到整个循环完成,你可以编写一个包装器来处理这个问题并同时表达你的意图:

template <typename T>
struct Stealing {
  std::unique_ptr<T> ptr;
  Stealing(std::unique_ptr<T>& ptr) : ptr(std::move(ptr)) {
  }   
  auto operator*() {
    return ptr.operator*();
  }   
  auto operator->() {
    return ptr.operator->();
  }   
}

您可以在循环中使用它作为 drop-in 替换 unique_ptr,如下所示:

for (Stealing<Kind> t: store) {
  // do what you like with t as if it was a std::unique_ptr
  // when t goes out of scope, so does its member -> Kind gets destroyed
}