对unique_ptrs集的原始指针查找

时间:2013-09-22 03:08:01

标签: c++ c++11 unique-ptr c++14

我经常发现自己想写这样的代码:

class MyClass
{
public:
  void addObject(std::unique_ptr<Object>&& newObject);

  void removeObject(const Object* target);

private:
  std::set<std::unique_ptr<Object>> objects;
};

然而,很多std :: set接口对std :: unique_ptrs都没用,因为查找函数需要std :: unique_ptr参数(我显然没有这些参数,因为它们由集合本身拥有)

我可以想到两个主要解决方案。

  1. 为查找创建临时unique_ptr。例如,上面的removeObject()可以实现为:

    void MyClass::removeObject(const Object* target)
    {
      std::unique_ptr<Object> targetSmartPtr(target);
      objects.erase(targetSmartPtr);
      targetSmartPtr.release();
    }
    
  2. 将原始指针映射替换为unique_ptrs。

      // ...
      std::map<const Object*, std::unique_ptr<Object>> objects;
    };
    
  3. 然而,对我来说,两者似乎都有点愚蠢。在解决方案1中,erase()不是noexcept,因此临时unique_ptr可能会删除它实际上不拥有的对象,而2需要不必要地为容器存储两倍。

    我了解Boost的指针容器,但与现代C ++ 11标准库容器相比,它们目前的功能有限。

    我最近在阅读有关C ++ 14的内容,并且遇到了“将异构比较查找添加到关联容器”。但是形成我对它的理解,查找类型必须与键类型相当,但原始指针不能与unique_ptrs相比。

    任何人都知道更优雅的解决方案或即将添加的C ++解决了这个问题吗?

6 个答案:

答案 0 :(得分:31)

如果存在std::set<Key>::findC++14 template就是Compare::is_transparent功能。您传入的类型不需要是Key,只需在比较器下等效。

所以写一个比较器:

template<class T>
struct pointer_comp {
  typedef std::true_type is_transparent;
  // helper does some magic in order to reduce the number of
  // pairs of types we need to know how to compare: it turns
  // everything into a pointer, and then uses `std::less<T*>`
  // to do the comparison:
  struct helper {
    T* ptr;
    helper():ptr(nullptr) {}
    helper(helper const&) = default;
    helper(T* p):ptr(p) {}
    template<class U, class...Ts>
    helper( std::shared_ptr<U,Ts...> const& sp ):ptr(sp.get()) {}
    template<class U, class...Ts>
    helper( std::unique_ptr<U, Ts...> const& up ):ptr(up.get()) {}
    // && optional: enforces rvalue use only
    bool operator<( helper o ) const {
      return std::less<T*>()( ptr, o.ptr );
    }
  };
  // without helper, we would need 2^n different overloads, where
  // n is the number of types we want to support (so, 8 with
  // raw pointers, unique pointers, and shared pointers).  That
  // seems silly:
  // && helps enforce rvalue use only
  bool operator()( helper const&& lhs, helper const&& rhs ) const {
    return lhs < rhs;
  }
};

然后使用它:

typedef std::set< std::unique_ptr<Foo>, pointer_comp<Foo> > owning_foo_set;

现在,owning_foo_set::find将接受unique_ptr<Foo>Foo*shared_ptr<Foo>(或Foo的任何派生类)并找到正确的元素。

在C ++ 14之外,您被迫使用mapunique_ptr方法或类似的方法,因为find的签名过于严格。或者编写自己的set等效文件。

答案 1 :(得分:2)

您可以尝试使用boost :: multi_index_container和Object *进行额外的索引。 像这样:

typedef std::unique_ptr<Object> Ptr;
typedef multi_index_container<
  Ptr,
  indexed_by<
    hashed_unique<Ptr>,
    ordered_unique<const_mem_fun<Ptr,Object*,&Ptr::get> >
  >
> Objects;

有关更多信息,请参阅Boost Multi-index Containers documentation

或者你可以在任何地方使用std :: shared_ptr,或者在set中使用原始指针吗?

为什么你需要通过原始pinter查找?如果你将它存储在任何地方并检查该指针的对象是否有效,那么最好使用std :: shared_ptr存储在容器中,std :: weak_ptr用于其他对象。在这种情况下,在使用之前,您根本不需要通过原始指针进行查找。

答案 2 :(得分:2)

虽然肯定是黑客攻击,但我才意识到可以构建一个临时的“哑”unique_ptr,其中包含新的放置而非风险取消分配。 removeObject()可以这样编写:

void MyClass::removeObject(const Object* target)
{
  alignas(std::unique_ptr<Object>)
  char dumbPtrData[sizeof(std::unique_ptr<Object>)];

  objects.erase(
      *::new (dumbPtrData) std::unique_ptr<Object>(const_cast<Object *>(target)));
}

此解决方案适用于std::unordered_setstd::map密钥和std::unordered_map密钥,全部仅使用标准C ++ 11,几乎没有不必要的开销。

答案 3 :(得分:1)

更新2: Yakk is correct,在没有明显妥协的情况下,无法使用标准C ++ 11容器执行此操作。在最坏的情况下,某些东西会以线性时间运行,或者您在问题中写下了那些变通方法。

我会考虑两种解决方法。

我会尝试排序std::vector,类似于boost::container::flat_set。是的,在最坏的情况下,插入/擦除将是线性时间。尽管如此,它可能比您想象的要快得多:与基于节点的容器(例如std::set)相比,连续容器对缓存非常友好。请阅读他们在boost::container::flat_set写的内容。这种妥协对你来说是否可以接受,我无法判断/衡量。

其他人也提到std::share_ptr。我个人试图避免它们,主要是因为“共享指针和全局变量一样好”(Sean Parent)。我不使用它们的另一个原因是因为它们很重,部分是因为我通常不需要的所有多线程的东西。但是,boost::shared_ptr在定义BOOST_SP_DISABLE_THREADS时会删除与多线程相关的所有开销。我相信在你的情况下使用boost::shared_ptr将是最简单的解决方案。


更新:作为Yakk kindly pointed out,我的方法具有线性时间复杂度...... :(



(第一个版本。)

您可以将自定义比较器传递给std::lower_bound()。这是一个基本实现如何:

#include <algorithm>
#include <cassert>
#include <iostream>
#include <memory>
#include <set>
#include <string>

using namespace std;

template <typename T>
class Set {

private:

    struct custom_comparator {
        bool operator()(const unique_ptr<T>& a, const T* const & b){
            return a.get() < b;
        }
    } cmp;

    set<unique_ptr<T>> objects; // decltype at begin() and end()
                                // needs objects to be declared here
public:

    auto begin() const -> decltype(objects.begin()) { return objects.begin(); }

    auto   end() const -> decltype(objects.end()  ) { return objects.end();   }

    void addObject(unique_ptr<T>&& newObject) {

        objects.insert(move(newObject));
    }

    void removeObject(const T* target) {

        auto pos = lower_bound(objects.begin(), objects.end(), target, cmp);

        assert (pos!=objects.end()); // What to do if not found?

        objects.erase(pos);
    }
};

void test() {

    typedef string T;

    Set<T> mySet;

    unique_ptr<T> a{new T("a")};
    unique_ptr<T> b{new T("b")};
    unique_ptr<T> c{new T("c")};

    T* b_ptr = b.get();

    mySet.addObject(move(a));
    mySet.addObject(move(b));
    mySet.addObject(move(c));

    cout << "The set now contains: " << endl;

    for (const auto& s_ptr : mySet) {

        cout << *s_ptr << endl;
    }

    mySet.removeObject(b_ptr);

    cout << "After erasing b by the pointer to it:" << endl;

    for (const auto& s_ptr : mySet) {

        cout << *s_ptr << endl;
    }
}

int main() {

    test();
}

答案 4 :(得分:0)

另一种可能性,接近于已接受的答案,但略有不同和简化。

我们可以利用以下事实:标准比较器std::less<>(无模板参数)是透明的。然后,我们可以在全局名称空间中提供我们自己的比较函数:

// These two are enough to be able to call objects.find(raw_ptr)
bool operator<(const unique_ptr<Object>& lhs, const Object* rhs) {
  return std::less<const Object*>()(lhs.get(), rhs);
}
bool operator<(const Object* lhs, const unique_ptr<Object>& rhs) {
  return std::less<const Object*>()(lhs, rhs.get());
}

class MyClass
{
  // ...

private:
  std::set<std::unique_ptr<Object>, std::less<>> objects;  // Note std::less<> here
};

答案 5 :(得分:-1)

你在这里使用独特的pinters。这意味着,您的集合具有对象的唯一所有权。现在,这应该意味着如果对象确实存在,它或者在集合中,或者你有唯一的指针。在这种情况下,您甚至不需要查找该集。

但对我而言,它看起来并非如此。在这种情况下,我想你最好使用共享指针。只需存储共享指针并传递它们,因为此集合旁边的人清楚地存储它们。