设计模式,用于创建对向量中元素的智能引用

Design pattern to create smart references to elements in a vector

本文关键字:元素 智能 引用 向量 用于 创建 设计模式      更新时间:2023-10-16

由于vector中的引用指向内存的位置而不是抽象元素,因此在更改vector的内存时可能会导致一些问题。

  1. 如果引用指向vector中的一个元素,然后将该元素洗牌到vector中的另一个位置,则引用不跟踪该元素,并且在洗牌后将指向错误的数据。

  2. 如果一个元素是无效的,你仍然可以访问该元素的内容,而不需要任何安全检查,如果你在元素无效之前声明了一个引用。

  3. 如果矢量大小调整,所有当前引用可能无效

我写了一个示例程序来演示这三个问题。

#include <iostream>
#include <vector>
struct entity {  //Simple struct of data.
     bool alive;
     float data;
};
class manager {
    std::vector<entity> vec;  
    size_t count; // Amount of currently alive entities
public:
    //Reserves initial_amount of entities, all set to dead, count set to 0.
    manager(size_t initial_amount) : vec(initial_amount, { false, 0.0f }), count(0) {} 
    entity& create(float f) {
        vec[count] = {true, f};
        return vec[count++];
    }
    void refresh() {                    //Two iterators, one starts at the front of the vector, the other at
        size_t front = 0;               //count. The front iterator searches for dead entities and swaps them
        size_t back  = count;           //with alive entities from the back iterator. For each swap we decrement
                                        //count by 1, with the final result being all alive entities are between
        while(true) {                   //0 and count.
            for( ; true; ++front) {
                if (front > back)      return;
                if (!vec[front].alive) break;
            }
            for( ; true; --back) {
                if (vec[back].alive) break;
                if (back <= front)   return;
            }
            std::swap(vec[back], vec[front]);
            --count;
            ++front;
            --back;
        }
    }
    void grow(size_t n) {
        vec.resize(n);
    }
    void print() { //Prints all alive entities.
        for (size_t index = 0; index < count; index++)
            std::cout << vec[index].data << " ";
        std::cout << std::endl;
    }
};
int main() {
    using namespace std;
    manager c(10);
    entity& d1 = c.create(5.5);
    entity& d2 = c.create(10.5);
    entity& d3 = c.create(7.5);
                             // Correct behavior
    cout << d1.data << endl; // 5.5
    cout << d2.data << endl; // 10.5
    cout << d3.data << endl; // 7.5
    cout << endl;
    d2.alive = false;        // "Kill" the entity
    c.refresh();             // removes all dead entities. (this will swap d2's and d3's data in the vector, 
                             // but wont change the locations they point to)
                             // Oh no! d2 and d3 still point to the same locations in the vector and now their data
                             // is incorrect after the swap, also d2 is dead maybe that should just be an error.
    cout << d1.data << endl; // 5.5
    cout << d2.data << endl; // 7.5
    cout << d3.data << endl; // 10.5
    cout << endl;
    c.print();               // Correct behavior, prints only alive entities.
    cout << endl;
    d3.data = 6.5;           // Trying to change the value of d3, which should still be alive.
    c.print();               // Error, because d3 still points to the 3rd slot the intended value hasn't been changed.
    cout << endl;
    c.grow(10000);
    cout << d1.data << endl; // After resize all these references are invalidated,
    cout << d2.data << endl; // and using them is undefined behavior.
    cout << d3.data << endl;
    return 0;
}
是否存在一种设计模式来创建智能引用或代理类型来解决这些问题?一个对象,将跟踪其元素在向量中的位置,特定的行为,如果元素是活的或死的,并在调整大小后保持有效?

我很好智能/代理引用的实现不是一个实际的引用,可以是一个指针,整数索引,或任何东西。但这是专门针对vector中的元素,而不是链表、map等。

使用std::vector<std::shared_ptr<entity>>,您可能拥有您想要的安全性:

class manager {
    std::vector<std::shared_ptr<entity>> vec;  
public:
    //Reserves initial_amount of entities
    explicit manager(size_t initial_amount) { vec.reserve(initial_amount); }
    std::weak_ptr<entity> create(float f) {
        vec.push_back(std::make_unique<entity>(entity{true, f}));
        return vec.back();
    }
    void refresh() {
        vec.erase(std::remove_if(vec.begin(), vec.end(),
                                 [](const auto& ent) {return !ent->alive;}),
                  vec.end());
    }
    void grow(size_t n) { vec.reserve(n); }
    void print() { //Prints all alive entities.
        for (const auto& ent : vec)
            std::cout << ent->data << " ";
        std::cout << std::endl;
    }
};

然后测试:

int main() {
    manager c(10);
    auto d1 = c.create(5.5);
    auto d2 = c.create(10.5);
    auto d3 = c.create(7.5);
    // Correct behavior
    if (auto e = d1.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 5.5
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 10.5
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 7.5
    std::cout << std::endl;
    if (auto e = d2.lock()) e->alive = false;        // "Kill" the entity
    c.refresh();             // removes all dead entities.
    if (auto e = d1.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 5.5
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // Die
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 10.5
    std::cout << std::endl;
    c.print();               // Correct behavior, prints only alive entities.
    std::cout << std::endl;
    if (auto e = d3.lock()) e->data = 6.5; // Trying to change the value of d3,
                                           // which should still be alive.
    c.print();
    std::cout << std::endl;
    c.grow(10000);
    if (auto e = d1.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 5.5
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // Die
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Dien"; // 6.5
}
演示