我经常发现自己想写这样的代码:
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参数(我显然没有这些参数,因为它们由集合本身拥有)
我可以想到两个主要解决方案。
为查找创建临时unique_ptr。例如,上面的removeObject()可以实现为:
void MyClass::removeObject(const Object* target)
{
std::unique_ptr<Object> targetSmartPtr(target);
objects.erase(targetSmartPtr);
targetSmartPtr.release();
}
将原始指针映射替换为unique_ptrs。
// ...
std::map<const Object*, std::unique_ptr<Object>> objects;
};
然而,对我来说,两者似乎都有点愚蠢。在解决方案1中,erase()不是noexcept,因此临时unique_ptr可能会删除它实际上不拥有的对象,而2需要不必要地为容器存储两倍。
我了解Boost的指针容器,但与现代C ++ 11标准库容器相比,它们目前的功能有限。
我最近在阅读有关C ++ 14的内容,并且遇到了“将异构比较查找添加到关联容器”。但是形成我对它的理解,查找类型必须与键类型相当,但原始指针不能与unique_ptrs相比。
任何人都知道更优雅的解决方案或即将添加的C ++解决了这个问题吗?
答案 0 :(得分:31)
如果存在std::set<Key>::find
,C++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之外,您被迫使用map
到unique_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_set
,std::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。这意味着,您的集合具有对象的唯一所有权。现在,这应该意味着如果对象确实存在,它或者在集合中,或者你有唯一的指针。在这种情况下,您甚至不需要查找该集。
但对我而言,它看起来并非如此。在这种情况下,我想你最好使用共享指针。只需存储共享指针并传递它们,因为此集合旁边的人清楚地存储它们。