一个累积其元素指标的容器

huangapple go评论68阅读模式
英文:

A container that accumulates its elements metrics

问题

我正在研究一种构建容器的解决方案,该容器除了基本功能之外还可以跟踪元素的存储大小。到目前为止,我还没有看到一种不会创建大量重复代码的解决方案,以使容器的每个无效成员都失效。这还假设存储的元素在存储后不会改变大小。

除非标准容器具有允许注入此行为的某些功能。以下示例应该可以正常工作,尽管为了简洁起见进行了缩写。所使用的声明如下:

typedef uint8_t   Byte;
typedef Byte  PacketId;

template <class T>
struct CollectionTraits {
    typedef T			  collection_type;
    typedef typename collection_type::value_type   value_type;
    typedef typename collection_type::size_type    size_type;
    typedef typename collection_type::iterator     iterator;
    typedef typename collection_type::reference    reference;
    typedef typename collection_type::const_iterator   const_iterator;

    const_iterator begin() const { return _collection.begin(); }
    const_iterator end() const  { return _collection.end(); }
    iterator begin() { return _collection.begin(); }
    iterator end()   { return _collection.end(); }

    size_type size() const { return _collection.size(); }

protected:
    T				 _collection;
};

struct Packet : CollectionTraits<std::vector<Byte>>
{   
   PacketId			  id;
};

struct  PacketList :  CollectionTraits<std::deque<Packet>>
{
public:
    typedef Packet::size_type			  data_size;

    void	 clear() { _collection.clear(); _total_size = 0; }

    data_size total_size() const  { return _total_size; }

    void push_back(const Packet& v) { 
        _collection.push_back(v); 
        _add(v);
    }

    void push_back(const Packet&& v) { 
        _collection.push_back(std::move(v)); 
        _add(v);
    }

    void push_front(const Packet& v) { 
        _collection.push_front(v); 
        _add(v);
    }

    void push_front(const Packet&& v) { 
        _collection.push_front(std::move(v)); 
        _add(v);
    }

    void pop_back() {
        _remove(_collection.back());
        _collection.pop_back(); 
    }

    void erase(const_iterator first, const_iterator last) {
        for(auto it = first; it != last; ++it) _remove(*it);
        _collection.erase(first, last);
    }    
    PacketList() : _total_size(0) {}
    PacketList(const PacketList& other) : _total_size(other._total_size) {}

private:
    void _add(const Packet& v)	  { _total_size += v.size(); }
    void _remove(const Packet& v)  { _total_size -= v.size(); }

    data_size 			_total_size;
};

结果中的接口应与标准容器类似。是否有一种方法可以避免这种大量重复的代码?是否有一种标准解决方案来解决这个问题?

英文:

I'm looking into a solution of building containers which track stored size of their elements in addition to basic functions.
So far I didn't saw a solution which doesn't create a huge amount of boilerplate code of each invalidating member of container. This also assumes that stored elements cannot change size after being stored.

Unless standard containers have some feature that allows to inject such behaviour. The following example should be working one, albeit abridged for brevity. The declarations used are:

typedef uint8_t   Byte;
typedef Byte  PacketId;
template &lt;class T&gt;
struct CollectionTraits {
typedef T			  collection_type;
typedef typename collection_type::value_type   value_type;
typedef typename collection_type::size_type    size_type;
typedef typename collection_type::iterator     iterator;
typedef typename collection_type::reference    reference;
typedef typename collection_type::const_iterator   const_iterator;
const_iterator begin() const { return _collection.begin(); }
const_iterator end() const  { return _collection.end(); }
iterator begin() { return _collection.begin(); }
iterator end()   { return _collection.end(); }
size_type size() const { return _collection.size(); }
protected:
T				 _collection;
};
struct Packet : CollectionTraits&lt;std::vector&lt;Byte&gt;&gt;
{   
PacketId			  id;
};

The container itself:

struct  PacketList :  CollectionTraits&lt;std::deque&lt;Packet&gt;&gt;
{
public:
typedef Packet::size_type			  data_size;
void	 clear() { _collection.clear(); _total_size = 0; }
data_size total_size() const  { return _total_size; }
void push_back(const Packet&amp; v) { 
_collection.push_back(v); 
_add(v);
}
void push_back(const Packet&amp;&amp; v) { 
_collection.push_back(std::move(v)); 
_add(v);
}
void push_front(const Packet&amp; v) { 
_collection.push_front(v); 
_add(v);
}
void push_front(const Packet&amp;&amp; v) { 
_collection.push_front(std::move(v)); 
_add(v);
}
void pop_back() {
_remove(_collection.back());
_collection.pop_back(); 
}
void erase(const_iterator first, const_iterator last) {
for(auto it = first; it != last; ++it) _remove(*it);
_collection.erase(first, last);
}    
PacketList() : _total_size(0) {}
PacketList(const PacketList&amp; other) : _total_size(other._total_size) {}
private:
void _add(const Packet&amp; v)	  { _total_size += v.size(); }
void _remove(const Packet&amp; v)  { _total_size -= v.size(); }
data_size 			_total_size;
};

The interface in result should similar to a standard container. Is there a way to avoid this amount of repeated code? Is there some standard solution for this problem?

答案1

得分: 1

不,std容器是代码生成器,用于构建一些数据结构,其速度与手动在C中编写它们一样快。

它们没有任意的钩子。如果您想构建更复杂的东西,您必须对它们进行封装并自己编写代码。

除非您需要使维护大小的数据像标准容器一样通用,并在许多情况下使用,否则您实际上不需要成为标准容器。标准容器是为在各种不同情况下使用而编写的;您的特定应用程序几乎肯定不会像标准容器设计用于的那么多方式使用您的数据。

模仿整个标准容器API可能是过度工程。

正如其他人所指出的,要在整个标准容器API上维护这种不变性是具有挑战性的。您的代码在许多方面都没有做到这一点。

  1. 它暴露了对包含数据的可变引用,这可能会使您的不变性失效。
  2. 它公开继承自标准容器,允许完全绕过您的API替代,无论是有意还是无意。
  3. 它仅替换了一部分API(推入但不是emplace,插入,甚至可能是splice,各种改变容器的重载)。

做这种事情的正常方式是在某个API内部存储一个容器,并不公开其存在(这是一个实现细节)。最多只提供非突变的迭代,甚至可能没有。

如果将执行的操作限制在实际需要的操作上,可以减少样板代码。

英文:

No, the std containers are code generators that build a few data structures that are about as fast as if you hand-coded them in C.

They don't have arbitrary hooks in them. If you want to build something more complex, you have to wrap them and write it yourself.

Unless you need your size-maintaining data to be as generic as a standard container and used in as many situations, you don't actually need to be a standard container. Standard containers are written to be used in a myriad of different situations; your specific application almost certainly won't be using your data in as many ways as standard containers are engineered to be used in.

Mimicing the entire standard container API is probably over-engineering.

As people have noted, maintaining this kind of invariant over the entire standard container API is challenging. Your code doesn't do it in a number of ways.

  1. It exposes mutable references to the contained data, which could invalidate your invariants.
  2. It exposes inheritance from the standard container publicly, which permits bypassing your API replacements completely either intentionally or by accident.
  3. It replaces only a subset of APIs (push but not emplace, insert, maybe even splice, various erase overloads) that mutate the container.

The normal way to do this kind of thing is to store a container within some API, and don't expose its existence (it is an implementation detail). Provide only non-mutating iteration at best, and maybe not even that.

If you restrict the operations performed sufficiently to those you actually need, you can keep the boilerplate down.

huangapple
  • 本文由 发表于 2023年2月19日 23:35:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75501255.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定