c++ OOP:类知道它在容器中的索引-防止覆盖

C++ OOP: Class knows its index in the container - prevent overwrite?

本文关键字:索引 覆盖 OOP c++      更新时间:2023-10-16

我有一个类idx_aware进入容器container,它包裹着std::vector。当这个类被添加到container时,container设置了一个指向idx_aware的指针,以及idx_aware在其内存中的索引。

除非container被销毁或idx_aware被删除,否则索引不会改变;idx_aware需要知道它的容器和索引,因为它有一些方法需要两者都工作。

现在这引入了以下问题:当我得到container中包含的idx_aware类的非const引用时,我可以为它分配另一个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;
}

一个可能的解决方法是改变with_idx,这样当set_idx被调用时,设置一个标志,防止赋值和复制操作符覆盖_i属性,像这样:

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)
  • 是否有一种设计模式可以更好地模拟这种情况?我的搜索没有成功。
  • 以这种方式重写赋值操作符还有其他不想要的结果吗?我在前面的例子中指出的限制看起来并不太"糟糕"。
  • 是否有更简单的解决方案?我想写一些代理对象来代替operator[]idx_aware &返回类型。

经验告诉我们,当c++不能做你想做的事情时,你很可能误用了OOP…

Robert的评论给了我这个解决方案。
为什么被包含的对象知道它的容器?能够执行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中的任何数据,并且包装器类可以使用需要与容器交互的所有方法进行扩展。

无需存储和保持索引最新或覆盖赋值操作符。