unique_ptrs集的原始指针查找
Raw pointer lookup for sets of unique_ptrs
我经常发现自己想写这样的代码:
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++
> 在 C++14 中,如果Compare::is_transparent
存在,std::set<Key>::find
是一个template
函数。 你传入的类型不需要是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
。
另一种可能性,接近公认的答案,但略有不同和简化。
我们可以利用标准比较器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
};
您可以尝试使用 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;
有关详细信息,请参阅提升多索引容器文档
或者你可以到处使用 std::shared_ptr,或者在 set 中使用原始指针?
为什么需要通过原始品特查找?如果您将其存储在任何位置并使用此指针检查该对象是否有效,则最好使用 std::shared_ptr 存储在容器中,使用 std::weak_ptr 存储其他对象。在这种情况下,在使用之前,您根本不需要通过原始指针进行查找。
虽然绝对是一个黑客,但我刚刚意识到可以用新的放置来构建一个临时的"愚蠢"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,几乎零不必要的开销。
更新 2:Yakk 是正确的,没有办法在没有重大妥协的情况下使用标准 C++11 容器做到这一点。在最坏的情况下,某些内容将以线性时间运行,或者您在问题中编写了这些解决方法。
我会考虑两种解决方法。
我会尝试排序std::vector
,类似于 boost::container::flat_set。是的,在最坏的情况下,插入/擦除将是线性时间。不过,它可能比您想象的要快得多:与基于节点的容器(例如std::set
)相比,连续容器对缓存非常友好。请阅读他们在boost::container::flat_set上写的内容。这种妥协对你来说是否可以接受,我无法判断/衡量。
其他人也提到了std::share_ptr
。我个人尽量避免它们,主要是因为"共享指针和全局变量一样好"(Sean Parent)。我不使用它们的另一个原因是因为它们重量很重,部分原因是我通常不需要的所有多线程东西。但是,定义BOOST_SP_DISABLE_THREADS
时,boost::shared_ptr
会消除与多线程相关的所有开销。我相信在您的情况下,使用boost::shared_ptr
将是最简单的解决方案。
更新:正如Yakk善意指出的那样,我的方法具有线性时间复杂度... :(
(第一个版本。
您可以通过将自定义比较器传递给 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();
}
您在这里使用独特的品画家。这意味着,您的集合具有对象的唯一所有权。现在,这应该意味着,如果对象确实存在,它要么在集合中,要么你有一个唯一的指针。在这种情况下,您甚至不需要查找集合。
但对我来说,情况似乎并非如此。我想在这种情况下,你最好使用共享指针。只需存储共享指针并传递它们,因为此集合旁边的人清楚地存储它们。
- 从堆栈分配的原始指针构造智能指针
- 将unique_ptr分配给原始指针
- 如何将唯一指针的 std::vector 转换为原始指针的 std::span?
- <Base> <Derived> 具有相同原始指针共享引用的 shared_ptr 和 shared_ptr 实例是否计数?
- C++原始指针和"delete"
- 为什么 C++ 地址中的矢量无法通过原始指针访问
- 如何正确实现具有原始指针的类的复制构造函数?
- 如何在将原始指针移动到基类构造函数之前从unique_ptr中提取原始指针
- 为包含原始指针的对象C++智能指针
- c++:复制、删除和运算符=在原始指针映射中
- C++模板,用于通过常量引用和原始指针传递向量
- C++为什么原始指针不会增加shared_ptr的引用计数?
- 从shared_ptr获取原始指针以将其传递给需要 raw 的函数
- 如何包装多级原始指针以赋予其容器语义
- 为什么我可以通过原始指针而不是shared_ptr来修改对象
- 将原始指针传递给接受unique_ptr作为参数的函数
- 为什么我们不允许将纯引用参数传递给 std::thread,但允许传递原始指针?
- 更改保留指向其字段的原始指针的对象地址
- 从原始指针(衰减的 C 样式数组)和大小生成范围::视图
- 如何在C++代码中灵活使用和替换标准::shared_ptr或标准::unique_ptr或原始指针?