2016-10-26 15 views
1

ベクトル内の参照は抽象要素ではなくメモリの位置を指すため、ベクトルのメモリを変更するときに問題が発生することがあります。ベクトル内の要素へのスマートな参照を作成するデザインパターン

  1. ベクトルの要素を基準点であれば、その後、その要素は、ベクトル内の別の場所にシャッフルされ、参照が要素を追跡していない、とシャッフルした後、間違ったデータを指すようになります。

  2. 要素が無効化されている場合、要素を無効化する前に参照を宣言した場合、その要素の内容に安全性チェックなしでアクセスできます。

  3. ベクトルのサイズを変更すると、すべての現在の参照が無効になることがあります。

私は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; 
} 

これらの問題を解決するスマートリファレンスまたはプロキシタイプを作成するためのデザインパターンはありますか?要素が生きているか死んでいて、サイズ変更後に有効なままであれば、ベクトル内の要素位置を追跡するオブジェクトです。

スマート/プロキシリファレンスの実装は実際の参照ではなく、ポインタ、整数インデックスなどにすることができます。しかし、これはstd::vector<std::shared_ptr<entity>>のベクトルではなく、リンクされたリストの要素、マップなどのために特別に

+2

言い換えれば、nope。しかし、ポインタのベクトルを持つことができます。 –

答えて

1

で、あなたが望むセキュリティ持っていることがあります。

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 << "Die\n"; // 5.5 
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // 10.5 
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // 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 << "Die\n"; // 5.5 
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // Die 
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // 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 << "Die\n"; // 5.5 
    if (auto e = d2.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // Die 
    if (auto e = d3.lock()) std::cout << e->data << std::endl; else std::cout << "Die\n"; // 6.5 
} 

Demo

関連する問題