【问题标题】:How can I find an element in a set which contains pointers to the elements?如何在包含指向元素的指针的集合中找到一个元素?
【发布时间】:2011-05-03 11:42:38
【问题描述】:

编辑:我修正了我的错误:我使用的是set,而不是vector

请考虑以下示例代码:

set<Foo *> set_of_foos;

set_of_foos.insert(new Foo(new Bar("x")));
set_of_foos.insert(new Foo(new Bar("y")));
[...]

// The way a "foo" is found is not important for the example.
bool find_foo(Foo *foo) {
  return set_of_foos.end() != set_of_foos.find(foo);
}

现在当我打电话时:

find_foo(new Foo(new Bar("x")));

该函数返回false,因为找不到我要查找的内容。原因对我来说很明显:指针指向不同的对象,因为它们都分配有new,导致地址的值不同。

但我想比较Foo 的内容(即上面示例中的"x")而不是Foo * 本身。使用 Boost 和修改 Foo 一样不是一种选择。

我是否需要遍历set_of_foos 内的每个Foo *,还是有更简单的解决方案?我尝试对每个Foo 的内容进行唯一序列化,并将set&lt;Foo *&gt; 替换为map&lt;string, Foo *&gt;,但这似乎是一个非常“黑客”的解决方案,效率不高。

【问题讨论】:

  • 细节点:std::vector 没有find() 成员函数;你的意思是std::find(vector_of_foos.begin(), vector_of_foos.end(), s)
  • std::vector 没有find 成员。你真的使用了不同的容器类型,还是全局函数std::find()
  • 在文本中更改了它,感谢您看到。我有一套,看错线了。

标签: c++ pointers stl set containers


【解决方案1】:

使用您的自定义可比较函数将您的 vector 更改为 set 以比较 Foo 对象。

应该是:

struct ltFoo
{
  bool operator()(Foo* f, Foo* s) const
  {
    return f->value() < s->value();
  }
};

set<Foo*, ltFoo> sFoo;
sFoo.insert(new Foo(new Bar("x"));
sFoo.insert(new Foo(new Bar("y"));

if (sFoo.find(new Foo(new Bar("y")) != sFoo.end())
{
    //exists
}
else
{
    //not exists
}

【讨论】:

  • 这是一个答案。请注意,它将集合更改为按用户定义的顺序而不是按地址排序。有一个隐含的假设,即在sFoo 中从来没有空指针或传递给sFoo。并且对find() 的调用中的Foo 不应该是newed。 (如果没有更多信息,很难说Bar。)
  • 这是一种有趣的方法。问题是,Foo 包含多少元素是动态大小的,例如2 Bar, 4 Bar, ... 我不确定如何为多个元素获得正确的匹配。
  • @DrColossos - 一个写得很糟糕的问题 -1 - 你的代码和陈述的假设每分钟都在变化。除了这里,你没有提到包含多个 Bars 的 Foo。人们怎么可能给出明智的答案?
  • 您可能不希望在堆上创建您的关键元素,它不会被删除。我更新了示例以使用堆栈上的关键对象。
【解决方案2】:

find_foo(new Foo(new Bar("x"))); 听起来不是一个好主意 - 它很可能(在任何情况下)都会导致该搜索功能的内存泄漏。

您可以将 find_if 与仿函数一起使用:

struct comparator {
    Foo* local;
    comparator(Foo* local_): local(local_) {}
    ~comparator() { /* do delete if needed */ } 
    bool operator()(const Foo* other) { /* compare local with other */ }
};

bool found = vec.end() != std::find_if(vec.begin(), vec.end(), comparator(new Foo(...)));

【讨论】:

  • 这取决于 OP 在相关时间调用delete 的谨慎程度。只要他在某个时刻遍历vector_of_foos 并在每个元素上调用delete,并且只要~Foo() 在任何成员变量指针上调用delete,一切都很好。
  • 内存泄漏等不是我真正关心的问题。这只是我在玩 C++。这一行将是代码的最后一行,所以没关系。但你是对的,在非实践示例中,delete 会更加小心。
  • @Oli:OP 没有将 find_foo 的参数添加到向量中,这确实是对给出的(伪)代码的任何合理解释的泄漏;听起来这个例子过于笼统了。
  • @Roger:啊,是的,我没有看到find_foo()...我绝对同意,在那种情况下!但是,这不是问题的答案。
  • 这里的Foo肯定是泄露的。创建的指针仅传递给find_foo()。给出了find_foo 的定义,它没有delete 它的参数。这看起来像 Java 习语,我怀疑发帖者只在必要时才需要学习在 C++ 中使用new
【解决方案3】:

我是否需要遍历 vector_of_foos 中的每个 Foo * 还是有更简单的解决方案?

您确实需要循环查找所需内容,但您可以使用 std::find_if 或其他“包装循环”。这在 C++0x 中使用 lambdas 更自然,但在 C++03 中,我只使用常规的 for 循环,如果您需要在多个地方执行此操作,可能会包裹在您自己的函数中。

【讨论】:

    【解决方案4】:

    不要使用 std::find,而是使用 std::find_if 并提供您自己的谓词。这当然取决于您是否能够访问 Foo 中包含“x”的成员。

    struct FooBar
    {
      FooBar(Foo* search) : _search(search){}
      bool operator(const Foo* ptr)
      {
        return ptr->{access to member} == _search->{access to member};
      }
    
      Foo* _search;
    }
    
    vector<Foo*>::iterator it = std::find_if(vec.begin(), vec.end(), FooBar(new Foo(new Bar("x")));
    

    如果您无法访问该成员,并且您可以保证所有其他成员都相同,您可以尝试在上述函子中使用裸 memcmp 而不是“==”。

    【讨论】:

      【解决方案5】:

      您也可以考虑使用Boost Ptr container library。它允许使用标准算法、查找等拥有一个指针列表,就好像它包含对象一样,并在向量删除时自动释放指针使用的内存。

      【讨论】:

        【解决方案6】:

        我有同样的问题,最后写了一个简单的 DereferenceCompare 类来完成这项工作。我很想知道其他人对此有何看法。问题的症结在于,现有答案要求程序员使用您的集合以一种不寻常的方式访问它,这种方式很容易导致内存泄漏,即将临时地址传递给std::set::find() 或通过std::find_if()。如果您要以非标准方式访问它,那么使用标准容器有什么意义? Boost 有一个很好的容器库可以解决这个问题。但是由于在 C++14 中引入了透明比较器,您可以编写一个自定义比较器,使 std::set::insert()std::set:find() 按预期工作,而不依赖于 Boost。您可以将其用作std::set&lt; Foo*, DereferenceCompare&lt;Foo, YourFooComparator&gt; &gt; set_of_foos;

        #ifndef DereferenceCompare_H
        #define DereferenceCompare_H
        
        #include <type_traits>
        
        // Comparator for std containers that dereferences pointer-like arguments.
        // Useful for containers of pointers, smart pointers, etc. that require a comparator.
        // For example:
        //   std::set< int*, DereferenceCompare<int> > myset1;
        //   int myint = 42;
        //   myset1.insert(&myint);
        //   myset1.find(&myint) == myset.end(); // false
        //   myset1.find(myint) == myset.end(); // false
        //   myset1.find(42) == myset.end(); // false
        //   myset1.find(24) == myset.end(); // true, 24 is not in the set
        //   std::set<int*> myset2;
        //   myset2.insert(&myint); // compiles, but the set will be ordered according to the address of myint rather than its value
        //   myset2.find(&myint) == myset.end(); // false
        //   myset2.find(a) == myset.end(); // compilation error
        //   myset2.find(42) == myset.end(); // compilation error
        //
        // You can pass a custom comparator as a template argument.  It defaults to std::less<T>.
        // The type of the custom comparator is accessible as DereferenceCompare::compare.
        // For example:
        //   struct MyStruct { int val; };
        //   struct MyStructCompare { bool operator() (const MyStruct &lhs, const MyStruct &rhs) const { return lhs.val < rhs.val; } };
        //   std::set< MyStruct*, DereferenceCompare<MyStruct, MyStructCompare> > myset;
        //   decltype(myset)::key_compare::compare comparator; // comparator has type MyStructCompare 
        template< typename T, class Compare = std::less<T> > class DereferenceCompare
        {
        #if __cplusplus==201402L // C++14
        private:
          // Less elegant implementation, works with C+=14 and later.
          template<typename U> static constexpr auto is_valid_pointer(int) -> decltype(*(std::declval<U>()), bool()) { return std::is_base_of<T, typename std::pointer_traits<U>::element_type>::value || std::is_convertible<typename std::remove_cv<typename std::pointer_traits<U>::element_type>::type, T>::value; }
          template<typename U> static constexpr bool is_valid_pointer(...) { return false; }
        
        public:
          template<typename U, typename V> typename std::enable_if<is_valid_pointer<U>(0) && is_valid_pointer<V>(0), bool>::type operator() (const U& lhs_ptr, const V& rhs_ptr) const { return _comparator(*lhs_ptr, *rhs_ptr); } // dereference both arguments before comparison
          template<typename U, typename V> typename std::enable_if<is_valid_pointer<U>(0) && !is_valid_pointer<V>(0), bool>::type operator() (const U& lhs_ptr, const V& rhs) const { return _comparator(*lhs_ptr, rhs); } // dereference the left hand argument before comparison
          template<typename U, typename V> typename std::enable_if<!is_valid_pointer<U>(0) && is_valid_pointer<V>(0), bool>::type operator() (const U& lhs, const V& rhs_ptr) const { return _comparator(lhs, *rhs_ptr); } // dereference the right hand argument before comparison
        #elif __cplusplus>201402L // Better implementation, depends on void_t in C++17.
        public:
          // SFINAE type inherits from std::true_type if its template argument U can be dereferenced, std::false otherwise.
          // Its ::value member is true if the type obtained by dereferencing U, i.e. the pointee, is either derived from T or convertible to T.
          // Its ::value is false if U cannot be dereferenced, or it the pointee is neither derived from nor convertible to T.
          // Example:
          //   DereferenceCompare<int>::has_dereference; // std::false_type, int cannot be dereferenced
          //   DereferenceCompare<int>::has_dereference<int>::is_valid_pointee; // false, int cannot be dereferenced
          //   DereferenceCompare<int>::has_dereference<int*>; // std::true_type, int* can be dereferenced to int
          //   DereferenceCompare<int>::has_dereference<int*>::is_valid_pointee; // true, dereferencing int* yields int, which is convertible (in fact, the same type as) int
          //   DereferenceCompare<int>::has_dereference< std::shared_ptr<int> >::is_valid_pointee; // true, the pattern also works with smart pointers
          //   DereferenceCompare<int>::has_dereference<double*>::is_valid_pointee; // true, double is convertible to int
          //   struct Base { }; struct Derived : Base { }; DereferenceCompare<Base>::has_dereference<Derived*>::is_valid_pointee; // true, Derived is derived from Base
          //   DereferenceCompare<int>::has_dereference<Derived*>; // std::true_type, Derived* can be dereferenced to Derived
          //   DereferenceCompare<int>::has_dereference<Derived*>::is_valid_pointee; // false, cannot convert from Derived to int nor does Derived inherit from int
          template< typename, class = std::void_t<> > struct has_dereference : std::false_type { static constexpr bool is_valid_pointee = false; };
          template< typename U > struct has_dereference< U, std::void_t<decltype(*(std::declval<U>()))> > : std::true_type { static constexpr bool is_valid_pointee = std::is_base_of<T, typename std::pointer_traits<U>::element_type>::value || std::is_convertible<typename std::remove_cv<typename std::pointer_traits<U>::element_type>::type, T>::value; };
        
          template<typename U, typename V> typename std::enable_if<has_dereference<U>::is_valid_pointee && has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs_ptr, const V& rhs_ptr) const { return _comparator(*lhs_ptr, *rhs_ptr); } // dereference both arguments before comparison
          template<typename U, typename V> typename std::enable_if<has_dereference<U>::is_valid_pointee && !has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs_ptr, const V& rhs) const { return _comparator(*lhs_ptr, rhs); } // dereference the left hand argument before comparison
          template<typename U, typename V> typename std::enable_if<!has_dereference<U>::is_valid_pointee && has_dereference<V>::is_valid_pointee, bool>::type operator() (const U& lhs, const V& rhs_ptr) const { return _comparator(lhs, *rhs_ptr); } // dereference the right hand argument before comparison
        #endif
        
        public:
          typedef /* unspecified --> */ int /* <-- unspecified */ is_transparent; // declaration required to enable polymorphic comparisons in std containers
          typedef Compare compare; // type of comparator used on dereferenced arguments
        
        private:
          Compare _comparator;
        };
        
        #endif // DereferenceCompare_H
        

        【讨论】:

          【解决方案7】:

          C++11

          如果您可以使用C++11 功能,那么您也可以使用lambda expression 而不是定义比较对象, 如其他答案所示。为了使下面的示例代码正常工作,我从您的代码中定义了BarFoo,如下所示:

          struct Bar {
              Bar(std::string s) : str(s) {}
              std::string str;
          };
          
          struct Foo {
              Foo(Bar* p) : pBar(p) {}
              Bar* pBar;
          };
          

          如果您将以下 lambda 表达式作为键比较函数提供给 std::set, 然后比较您的内容(即字符串"x""y")而不是指向内容的指针。 因此,find() 也可以按预期工作,如以下代码所示:

          int main() {
              auto comp = [](const Foo* f1, const Foo* f2) { return f1->pBar->str < f2->pBar->str; };
              std::set<Foo*, decltype(comp)> set_of_foos(comp);
          
              set_of_foos.emplace(new Foo(new Bar("x")));
              set_of_foos.emplace(new Foo(new Bar("y")));
          
              auto it = set_of_foos.find(new Foo(new Bar("x")));
              if (it == std::end(set_of_foos))
                  std::cout << "Element not found!" << std::endl;
              else
                  std::cout << "Element found: " << (*it)->pBar->str << std::endl;
          
              return 0;
          }
          

          输出:

          找到的元素:x

          Code on Ideone

          注意:std::set 只允许唯一的条目(即键)。条目是否唯一取决于提供的键比较功能。 对于上面的代码,这意味着您只能使用pBar-&gt;str == "x" 存储单个条目,即使BarFoo 存储在不同的地址。 如果您想使用pBar-&gt;str == "x" 存储多个条目(例如),那么您必须使用std::multiset

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-09-26
            • 2011-03-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多