2016-09-06 55 views
0

我有一个类idx_aware,进入一个容器container,它围绕着std::vector。当该类被添加到container时,container在其内部存储器存储器中设置指向自身的指针idx_aware以及idx_aware索引。C++ OOP:Class知道它在容器中的索引 - 防止覆盖?

索引不会改变,直到container被销毁或idx_aware被删除; idx_aware需要知道它的容器及其索引,因为它有一些方法需要这两个工作。

现在,这引入了以下问题:当我得到一个非const引用包含在containeridx_aware类,我可以分配给它的另一个idx_aware类,它可以有不同的索引。目的是分配所有的字段并保持索引原样。

#include <vector> 
#include <limits> 
#include <iostream> 

class container; 


// Stores a std::size_t field, which can be set only by subclasses. 
class with_idx { 
    std::size_t _i; 
public: 
    with_idx() : _i(std::numeric_limits<std::size_t>::max()) {} 
    operator std::size_t() const { return _i; } 
protected: 
    void set_idx(std::size_t i) { _i = i; } 
}; 


// Knows its index and its container 
class idx_aware : public with_idx { 
    container const *_container; 
    int _some_field1; 
    float _some_field2; 
public: 
    void foo() { 
     // Do stuff using _container and _i 
    } 
private: 
    friend class container; 
}; 


// Wraps around a std::vector 
class container { 
    std::vector<idx_aware> _data; 
public: 

    idx_aware &operator[](std::size_t idx) { 
     // Need non-const access to call foo 
     return _data[idx]; 
    } 

    idx_aware const &operator[](std::size_t idx) const { 
     return _data[idx]; 
    } 

    std::size_t add(idx_aware const &item) { 
     // Here it could potentially reuse a freed position 
     std::size_t free_slot = _data.size(); 
     // Ensure _data is big enough to contain free_slot 
     if (_data.size() <= free_slot) { 
      _data.resize(free_slot + 1); 
     } 
     // Assign 
     _data[free_slot] = item; 
     _data[free_slot].set_idx(free_slot); 
     _data[free_slot]._container = this; 
     return free_slot; 
    } 

}; 

int main() { 
    container c; 
    idx_aware an_item; 
    std::size_t i = c.add(an_item); 

    std::cout << c[i] << std::endl; // Prints 0 

    idx_aware another_item; // Created from somewhere else 

    // I want to set all the data in idx_aware, but the 
    // index should stay the same! 

    c[i] = another_item; 

    std::cout << c[i] << std::endl; // Prints numeric_limits<size_t>::max() 

    // Now container[i] is broken because it doesn't know anymore its index. 

    return 0; 
} 

一个可能的解决方法是在防止分配和复制操作来覆盖_i属性,这样使得当set_idx被调用时,设置一个标志,以改变with_idx

class with_idx { 
    std::size_t _i; 
    bool _readonly; 
public: 
    with_idx() : _i(std::numeric_limits<std::size_t>::max()), _readonly(false) {} 
    with_idx(with_idx const &other) : _i(other._i), _readonly(false) {} 
    with_idx &operator=(with_idx const &other) { 
     if (!_readonly) { 
      _i = other._i; 
     } 
     return *this; 
    } 
    operator std::size_t() const { return _i; } 
protected: 
    void set_idx(std::size_t i) { 
     _i = i; 
     if (i != std::numeric_limits<std::size_t>::max()) { 
      // This has been set by someone with the right to do so, 
      // prevent overwriting 
      _readonly = true; 
     } else { 
      // Removed from the container, allow overwriting 
      _readonly = false; 
     } 
    } 
}; 

这会导致在分配后返回索引不变的idx_aware类的引用。

idx_aware &not_in_container1 = /* ... */; 
idx_aware &not_in_container2 = /* ... */; 
idx_aware &in_container = /* ... */; 

not_in_container1 = in_container = not_in_container2; 
// std::size_t(not_in_container_1) != std::size_t(not_in_container_2) 
  • 是否有能够以更好的方式这种情况建模设计模式?我的搜索没有成功。
  • 以这种方式覆盖赋值运算符是否会产生其他不良后果?我在前面的例子中指出的限制看起来不太“糟糕”。
  • 有没有更简单的解决方案?我想过写一些代理对象来代替idx_aware &返回类型operator[]

经验是说,当C++不这样做,你打算什么,你很可能是滥用OOP ...

+1

我根本就不允许通过赋值操作符来改变索引。 – MikeMB

+1

虽然这样做可能有很好的理由,但我建议(重新)评估是否需要索引和容器地址的with_idx和idx_aware中的功能在可能的情况下移动到公共接口。容器的物体必须知道有关容器的事实有点可疑。 –

回答

0

罗伯特的评论暗示我这个解决方案。
为什么包含的对象知道它的容器?能够执行诸如foo之类的操作并提供其他方式需要访问容器的速记方法。

让我们从包含的对象中去除这个功能;包含的对象只是数据有效载荷。相反,我们让operator[]不返回包含的对象,而是返回某种迭代器,包含所包含对象的包装器,它知道容器和索引,一旦解除引用就返回实际包含的对象。

class was_idx_aware { 
    int _some_field1; 
    float _some_field2; 
}; 

class container { 
    std::vector<idx_aware> _data; 
public: 
    class idx_aware_wrapper { 
     container const *_container; 
     std::size_t _idx; 
    public: 
     idx_aware_wrapper(container const &c, std::size_t i) 
      : _container(&c) 
      , _idx(i) 
     {} 

     was_idx_aware const &operator*() const { 
      return _container->_data[_idx]; 
     } 

     was_idx_aware &operator*() { 
      return _container->_data[_idx]; 
     } 

     void foo() { 
      // Do stuff using _container and _idx. 
     } 
    }; 


    idx_aware_wrapper operator[](std::size_t i) { 
     return idx_aware_wrapper(*this, i); 
    } 

    /* .... */ 
}; 

这使得was_idx_aware快速访问任何数据,而包装类可以与所有需要与容器相互作用的方法来增强。无需存储并保持索引最新或覆盖赋值运算符。