通过对象比较在集合中查找C ++对象而不是使用仿函数

时间:2017-02-18 16:57:01

标签: c++ stl stl-algorithm

我想填充top: 50%std::set个对象,并检查该集合中是否存在具有相同值的另一个GraphNode。在Java中,可以通过重载equals和compareTo方法来比较对象,而不是创建一些仿函数对象。我实现了GraphNode并希望在集合中找到这样的对象,

operator==(T& t)

但我没有在std::find(nodesSet->begin(),nodesSet->end(), new GraphNode<T>(1))!=nodesSet->end()) ==运算符函数中得到断点。为什么会这样?有没有办法通过对象比较找到对象?

()()

3 个答案:

答案 0 :(得分:2)

正如 aschepler 所指出的那样,代码的问题在于你最终会比较指针,而不是对象。 std::find(查看链接页面中可能的实现),如果在没有谓词的情况下调用,则使用==运算符来比较当您给它的迭代器被解除引用时返回的内容。在您的情况下,您有一个std::set<GraphNode<T>*> nodesSet,因此*nodesSet.begin()的类型为GraphNode<T>*,而不是GraphNode<T>(请注意缺少明星)。为了使您能够使用为==定义的GraphNode运算符,您需要将您的集合设为std::set<GraphNode<T>>,这是您的类型的对象而不是指针。

如果必须在集合中存储指针(例如,因为您不想复制对象),则可以为指针编写一个包装器,该指针使用指针的基础类的比较运算符。这是一个例子:

#include <iostream>
#include <set>
#include <algorithm>

class obj {
  int i;
public:
  obj(int i): i(i) { }
  bool operator<(const obj& o) const { return i < o.i; }
  bool operator==(const obj& o) const { return i == o.i; }
  int get() const { return i; }
};

template <typename T>
class ptr_cmp {
  T* p;
public:
  ptr_cmp(T* p): p(p) { }
  template <typename U>
  bool operator<(const ptr_cmp<U>& o) const { return *o.p < *p; }
  template <typename U>
  bool operator==(const ptr_cmp<U>& o) const { return *o.p == *p; }
  T& operator*() const { return *p; }
  T* operator->() const { return p; }
};

int main(int argc, char* argv[])
{
  obj five(5), seven(7);

  std::set<ptr_cmp<obj>> s;
  s.insert(&five);
  s.insert(&seven);

  obj x(7);

  std::cout << (*std::find(s.begin(),s.end(), ptr_cmp<obj>(&x)))->get()
            << std::endl;

  return 0;
}

事实证明,我的编译器(gcc 6.2.0)要求operator==operator< std::find在没有谓词的情况下工作。

虽然使用谓词有什么问题?这是一种更通用的方法。这是一个例子:

#include <iostream>
#include <set>
#include <algorithm>

class obj {
  int i;
public:
  obj(int i): i(i) { }
  bool operator==(const obj& o) const { return i == o.i; }
  int get() const { return i; }
};

template <typename T>
struct ptr_cmp {
  const T *l;
  ptr_cmp(const T* p): l(p) { }
  template <typename R>
  bool operator()(const R* r) { return *l == *r; }
};
template <typename T>
ptr_cmp<T> make_ptr_cmp(const T* p) { return ptr_cmp<T>(p); }

int main(int argc, char* argv[])
{
  obj five(5), seven(7);

  std::set<obj*> s;
  s.insert(&five);
  s.insert(&seven);

  obj x(7);

  std::cout << (*std::find_if(s.begin(),s.end(), make_ptr_cmp(&x)))->get()
            << std::endl;

  return 0;
}

注意,make_ptr_cmp允许您避免明确说明类型,因此您可以编写通用代码。

如果你可以使用C ++ 11,使用可以只使用lambda函数而不是ptr_cmp

std::find_if(s.begin(),s.end(), [&x](const obj* p){ return *p == x; } )

答案 1 :(得分:1)

std::find比较迭代器指向的值。这些值是指针,而不是对象。所以它们都不等于new GraphNode<T>(1),这是指向全新物体的全新指针。

答案 2 :(得分:1)

正如其他人所说,你正在比较指针,它们不会按预期工作,它会对内存中的地址进行比较。操作a < b对于指针具有有效含义,但是将按元素在内存中的位置对元素进行排序,而不是对其包含的数据元素进行排序,并且没有元素将是唯一的,因为它们都将具有唯一的地址。这是除非你尝试两次插入相同的元素。

然而,使用std::find将隐藏上述问题,无论如何迭代容器中的所有元素。如果您使用的是set,那么您应该渴望获得元素的对数时间查找,因此应该使用set自己的find函数,该函数知道它是一个二叉树下的罩。

在C ++中,Object#equals的等价物是operator==(如您所知),在关联容器的上下文中,Object#compareTo的等价物是operator<Object#equalsoperator==以同样的方式工作,完全符合您的预期;如果某些事情等于平等,易于理解。算法以不同的方式使用Object#compareTooperator<operator<用于实现strict weak ordering以确定一个元素是否小于或大于另一个元素。

因此,为了让您的元素在set中可用,您需要在operator<课程中重写GraphNode。完成后,您可以使用std::set::find函数查找集合中的元素,它将在O(log n)时间而不是线性时间内找到它们。

这些算法是基于他们正在处理值类型的假设而设计的,即不是指针而是指向那些指针的东西。因此,要使用指针,您需要定义一个新的比较函数,该函数在应用比较之前基本取消引用指针(==<)。

一些示例代码

#include <algorithm>
#include <iostream>
#include <set>
#include <vector>

template<typename>
class Graph
{
};

template<class T>
class GraphNode
{
    friend class Graph<T>;
    friend bool operator==(const GraphNode<T>& a, const GraphNode<T>& b);

    private:
        T t;
        std::vector<GraphNode<T>*> adjNodes;

    public:
        explicit GraphNode(const T& tval)
            :t(tval)
        {}
        T& getT(){ return t; }
        const T& getT() const { return t; }
        bool operator==(const T& t);

        friend bool operator<(const GraphNode& a, const GraphNode& b){
            return a.t < b.t;
        }
};

template<class T>
inline bool GraphNode<T>::operator==(const T& t)
{
    return (this->t == t);
}

template<class T>
inline bool operator==(const GraphNode<T>& a, const GraphNode<T>& b)
{
    return (a.t == b.t);
}

int main()
{
    using IntGraphNode = GraphNode<int>;

    std::set<IntGraphNode> nodesSet;

    nodesSet.insert(IntGraphNode(1));
    nodesSet.insert(IntGraphNode(2));

    auto findit = nodesSet.find(IntGraphNode(1));

    if(findit != nodesSet.end())
    {
        std::cout << "found value\n";
    }

    auto findit2 = std::find_if(
                        nodesSet.begin(),
                        nodesSet.end(),
                        [](IntGraphNode i) { return i.getT() == 1;});

    if(findit2 != nodesSet.end())
    {
        std::cout << "found value aswell\n";
    }
}

第一次搜索使用set自己的查找函数,第二次搜索使用std::find_if,它使用谓词(返回true或false的函数)来测试相等性。第二个例子还通过公开T对象并在比较lambda函数中使用它来消除了制作虚拟对象的需要。

还有关于

的评论
std::find(nodesSet->begin(),nodesSet->end(), new GraphNode<T>(1))!=nodesSet->end())

这一方面存在很多概念上的误解。首先std::find没有采用比较函数,即std::find_if,但编译器会告诉你(以它自己特别是间接和冗长的方式)。此外,在算法中评估比较函数,您试图在呼叫站点评估它。另一件事与java不同,你不能只是忘掉new对象。这是内存泄漏,您不再有任何存储new ed值的变量,因此您无法delete它。