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

时间:2011-04-12 05:43:34

标签: c++ composition

假设我的应用程序需要一个新类型,它由一个std::vector<int>扩展,由一个函数扩展。直接的方式是组合(由于STL容器的继承限制):

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

这要求用户首先在构造函数中构造vector<int>和副本,这在我们要处理大量的大向量时是不好的。当然,人们可以写一个传递给push_back(),但这引入了可变状态,我想避免。

所以在我看来,我们可以避免复制或保持A不可变,这是正确的吗?

如果是这样,最简单的(效率等效)方法是在命名空间范围内使用typedef和free函数:

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

这在某种程度上感觉不对,因为将来的扩展会“污染”命名空间。此外,可以在任何a_hash(...)上调用vector<int>,这可能会导致意外结果(假设我们对用户必须遵循的A施加约束,或者在第一个示例中强制执行)

我的两个问题是:

  • 在使用上述类代码时,如何不牺牲不变性和效率?
  • 什么时候使用自由函数而不是类/结构中的封装?

谢谢!

3 个答案:

答案 0 :(得分:6)

哈希算法不是一种类型的算法,也可能不应该限制在任何特定容器类型中的数据。如果你想提供散列,最好的做法是创建一个函数来计算一个哈希值元素(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());

请注意,这允许collvectordeque,或者您可以使用一对istream_iterators来对文件中的数据进行哈希处理... < / p>

答案 1 :(得分:0)

一个简单的解决方案是将私有成员变量声明为参考&amp;在构造函数中初始化。这种方法引入了一些限制,但在大多数情况下它是一个很好的选择。

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'
};

答案 2 :(得分:0)

Ad immutable:您可以使用vector的范围构造函数并创建一个输入迭代器来为向量提供内容。范围构造函数只是:

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

发电机有点棘手。如果您现在有一个使用push_back构造向量​​的循环,则需要进行相当多的重写才能转换为从方法一次返回一个项目的对象。您需要在有效的输入迭代器中包装对它的引用。

广告免费功能:由于超载,污染命名空间通常不是问题,因为只有具有特定参数类型的调用才会考虑该符号。

自由函数也使用参数依赖查找。这意味着函数应该放在类所在的命名空间中。比如:

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

现在你仍然可以打电话给hash不合格,但除非你做using namespace std,否则不要将其用于任何其他目的(我个人几乎从不这样做,而且只是使用std::前缀或执行using std::vector以获得我想要的符号。不幸的是,我不确定依赖于命名空间的查找如何与另一个命名空间中的typedef一起使用。

在许多模板算法中,通常使用自由函数 - 并且具有相当通用的名称 - 而不是方法,因为它们可以添加到现有类中,可以为基本类型或两者定义。