正确的 API 用于访问容器 <T> 的数据成员

Proper API for access data members of container<T>

我有以下 class:

    class Document
    {
    public:
        Document():
         // default values for members, 
         // ...
         m_dirty{false}{}

        // Accessor functions

        template<class OutputStream>
        Document& save(OutputStream stream)
        {
            // Write stuff to `stream`
            // ...

            m_dirty = false;
            return *this;
        }

        bool dirty() const { return m_dirty; }

    private:
        Size2d m_canvas_size;
        LayerStack m_layers;
        LayerIndex m_current_layer;

        std::vector<Palette> m_palettes;
        PaletteIndex m_current_palette;
        ColorIndex m_current_color;

        std::vector<std::string> m_palette_names;
        std::vector<std::string> m_layer_names;

        bool m_dirty;
    };

class 是否应该有 public 成员函数来直接修改 say m_palettes 的元素,比如

Document& color(PaletteIndex, ColorIndex, Color)

,还是更“正确”,只允许通过一对 API:s

访问整个向量
std::vector<Palette> const& palettes();
Document& palettes(std::vector<Palette>&&);

第一个选项会更有效,因为它不需要创建数据成员的临时副本,但持续使用此设计会使界面臃肿。 class.

中的每个容器都需要“深度”getter 和 setter

注意脏标志。因此,以下将打破抽象:

std::vector<Palette>& palettes();

您可能有 Proxy 从 Palette 修改中“传播”脏标志,例如:

template <typename T>
class DirtyProxy
{
    T& data;
    bool& dirty;
public:
    DirtyProxy(T& data, bool& dirty) : data(data), dirty(dirty) {}
    ~DirtyProxy() { dirty = true;}
    DirtyProxy(const DirtyProxy&) = delete;

    T* operator ->() { return data; }
};

然后

DirtyProxy<Palette> palette(std::size_t i) { return {m_palettes.at(i), dirty}; }

我认为解决它的最稳健的方法是使用回调。代理的一个问题是它不会处理客户端代码抛出异常的情况(假设有强异常保证)。测试用例:

try
{
    auto property_proxy = obj.getProperty();
    // an exception is thrown here...
    property_proxy->val = x;  // Never updated

}
catch(...)
{}

assert(!obj.dirty());

会失败,因为 dtor 总是设置脏标志。但是有回调

class Foo
{
    public:
        template<class F>
        Foo& modifyInSitu(F&& f)
        {
            f(x);
            m_dirty = true;
            return *this
        }
};

只会在 f(x) 不抛出时更新 m_dirty