简体   繁体   English

通过组合或自由功能扩展STL容器?

[英]Extension of STL container through composition or free functions?

Say I need a new type in my application, that consists of a std::vector<int> extended by a single function. 假设我在我的应用程序中需要一个新类型,它由一个由单个函数扩展的std::vector<int> The straightforward way would be composition (due to limitations in inheritance of STL containers): 最直接的方法是组合(由于STL容器继承的限制):

class A {
    public:
        A(std::vector<int> & vec) : vec_(vec) {}
        int hash();
    private:
        std::vector<int> vec_
}

This requires the user to first construct a vector<int> and a copy in the constructor, which is bad when we are going to handle a sizeable number of large vectors. 这要求用户首先在构造函数中构造一个vector<int>和一个副本,当我们要处理大量的大向量时,这很不好。 One could, of course, write a pass-through to push_back() , but this introduces mutable state, which I would like to avoid. 当然,可以将传递代码写入push_back() ,但这会引入可变状态,我想避免这种状态。

So it seems to me, that we can either avoid copies or keep A immutable, is this correct? 因此在我看来,我们可以避免复制或保留A不变,这是正确的吗?

If so, the simplest (and efficiency-wise equivalent) way would be to use a typedef and free functions at namespace scope: 如果是这样,最简单的方法(以及等效的效率方法)将是在命名空间范围内使用typedef和free函数:

namespace N {
typedef std::vector<int> A;
int a_hash(const A & a);
}

This just feels wrong somehow, since extensions in the future will "pollute" the namespace. 只是感觉有点不对劲,因为将来的扩展会“污染”命名空间。 Also, calling a_hash(...) on any vector<int> is possible, which might lead to unexpected results (assuming that we impose constraints on A the user has to follow or that would otherwise be enforced in the first example) 另外,可以在任何vector<int>上调用a_hash(...) ,这可能会导致意外结果(假设我们对用户必须遵循的A施加约束,否则在第一个示例中将强制执行)

My two questions are: 我的两个问题是:

  • how can one not sacrifice both immutability and efficiency when using the above class code? 使用上述类代码时,如何不牺牲不变性和效率?
  • when does it make sense to use free functions as opposed to encapsulation in classes/structs? 什么时候使用自由函数而不是封装在类/结构中有意义?

Thank you! 谢谢!

Hashing is an algorithm not a type, and probably shouldn't be restricted to data in any particular container type either. 散列不是一种算法,也不应该局限于任何特定容器类型中的数据。 If you want to provide hashing, it probably makes the most sense to create a functor that computes a hash one element ( int , as you've written things above) at a time, then use std::accumulate or std::for_each to apply that to a collection: 如果您想提供散列,可能最有意义的是创建一个算子,一次计算一个散列元素( int ,正如您上面已经写过的一样),然后使用std::accumulatestd::for_each将其应用于集合:

namespace whatever { 
struct hasher { 
    int current_hash;
public:
    hasher() : current_hash(0x1234) {}

    // incredibly simplistic hash: just XOR the values together.
    operator()(int new_val) { current_hash ^= new_val; }
    operator int() { return current_hash; }
};
}

int hash = std::for_each(coll.begin(), coll.end(), whatever::hasher());

Note that this allows coll to be a vector , or a deque or you can use a pair of istream_iterators to hash data in a file... 请注意,这允许coll成为vectordeque或者您可以使用一对istream_iterators对文件中的数据进行哈希处理。

One simple solution is to declare the private member variable as reference & initialize in constructor. 一种简单的解决方案是将私有成员变量声明为引用并在构造函数中初始化。 This approach introduces some limitation, but it's a good alternative in most cases. 这种方法引入了一些限制,但是在大多数情况下是一个很好的选择。

class A {
    public:
        A(std::vector<int> & vec) : vec_(vec) {}
        int hash();
    private:
        std::vector<int> &vec_; // 'vec_' now a reference, so will be same scoped as 'vec'
};

Ad immutable: You could use the range constructor of vector and create an input iterator to provide the content for the vector. 广告不可变:您可以使用vector的range构造函数并创建一个输入迭代器来提供vector的内容。 The range constructor is just: 范围构造函数只是:

template <typename I>
A::A(I const &begin, I const &end) : vec_(begin, end) {}

The generator is a bit more tricky. 生成器比较棘手。 If you now have a loop that constructs a vector using push_back, it takes quite a bit of rewriting to convert to object that returns one item at a time from a method. 如果现在有一个循环使用push_back构造向量​​,则需要大量的重写才能转换为一次从方法返回一项的对象。 Than you need to wrap a reference to it in a valid input iterator. 比您需要在有效的输入迭代器中包装对它的引用。

Ad free functions: Due to overloading, polluting the namespace is usually not a problem, because the symbol will only be considered for a call with the specific argument type. 无广告功能:由于重载,污染名称空间通常不是问题,因为只有在使用特定参数类型的调用中才会考虑使用符号。

Also free functions use the argument-dependent lookup. 自由函数也使用依赖于参数的查找。 That means the function should be placed in the namespace the class is in. Like: 这意味着函数应该放在类所在的名称空间中。

#include <vector>
namespace std {
    int hash(vector<int> const &vec) { /*...*/ }
}
//...
std::vector<int> v;
//...
hash(v);

Now you can still call hash unqualified, but don't see it for any other purpose unless you do using namespace std (I personally almost never do that and either just use the std:: prefix or do using std::vector to get just the symbol I want). 现在您仍然可以将hash称为无条件hash ,但是除非您using namespace std否则using namespace std其他用途(我个人几乎从不这样做,而只是使用std::前缀或using std::vector获得我想要的符号)。 Unfortunately I am not sure how the namespace-dependent lookup works with typedef in another namespace. 不幸的是,我不确定与名称空间相关的查找如何与另一个名称空间中的typedef一起使用。

In many template algorithms, free functions—and with fairly generic names—are often used instead of methods, because they can be added to existing classes, can be defined for primitive types or both. 在许多模板算法中,通常使用自由函数(具有相当通用的名称)来代替方法,因为它们可以添加到现有类中,可以为原始类型定义,也可以为两者都定义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM