英文:
Using std::vector<std::any> to store heterogenous unique_ptrs
问题
I want to store differently typed unique_ptrs in a vector.
I attempted using std::any as follows.
#include <vector>
#include <any>
#include <memory>
class A
{
int val;
};
class B
{
float val;
};
int main()
{
std::vector<std::any> vec;
auto a = new A();
auto b = new B();
vec.push_back(std::unique_ptr<A>(a));
vec.push_back(std::unique_ptr<B>(b));
}
It is failing as follows.
main.cpp: In function 'int main()':
main.cpp:23:18: error: no matching function for call to 'std::vector<std::any>::push_back(std::unique_ptr<A>)'
23 | vec.push_back(std::unique_ptr<A>(a));
| ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/local/include/c++/12.1.0/vector:64,
from main.cpp:1:
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1276 | push_back(const value_type& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:35: note: no known conversion for argument 1 from 'std::unique_ptr<A>' to 'const std::vector<std::any>::value_type&' {aka 'const std::any&'}
1276 | push_back(const value_type& __x)
| ~~~~~~~~~~~~~~~~~~^~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(value_type&&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1293 | push_back(value_type&& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:30: note: no known conversion for argument 1 from 'std::unique_ptr<A>' to 'std::vector<std::any>::value_type&&' {aka 'std::any&&'}
1293 | push_back(value_type&& __x)
| ~~~~~~~~~~~~~^~~
main.cpp:24:18: error: no matching function for call to 'std::vector<std::any>::push_back(std::unique_ptr<B>)'
24 | vec.push_back(std::unique_ptr<B>(b));
| ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1276 | push_back(const value_type& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:35: note: no known conversion for argument 1 from 'std::unique_ptr<B>' to 'const std::vector<std::any>::value_type&' {aka 'const std::any&'}
1276 | push_back(const value_type& __x)
| ~~~~~~~~~~~~~~~~~~^~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(value_type&&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1293 | push_back(value_type&& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:30: note: no known conversion for argument 1 from 'std::unique_ptr<B>' to 'std::vector<std::any>::value_type&&' {aka 'std::any&&'}
Is this possible with std::any? Or is there any alternative? I am unable to use std::variant as I don't know all the types to be stored upfront.
Edit:
I just want to use the unique_ptr vector to enforce that objects will be cleaned up at program exit (the vector will be alive till program exit). The consumer code will use direct references to a and b so I don't need to access/enumerate object references via the vector.
英文:
I want to store differently typed unique_ptrs in a vector.
I attempted using std::any as follows.
#include <vector>
#include <any>
#include <memory>
class A
{
int val;
};
class B
{
float val;
};
int main()
{
std::vector<std::any> vec;
auto a = new A();
auto b = new B();
vec.push_back(std::unique_ptr<A>(a));
vec.push_back(std::unique_ptr<B>(b));
}
It is failing as follows.
main.cpp: In function 'int main()':
main.cpp:23:18: error: no matching function for call to 'std::vector<std::any>::push_back(std::unique_ptr<A>)'
23 | vec.push_back(std::unique_ptr<A>(a));
| ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/local/include/c++/12.1.0/vector:64,
from main.cpp:1:
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1276 | push_back(const value_type& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:35: note: no known conversion for argument 1 from 'std::unique_ptr<A>' to 'const std::vector<std::any>::value_type&' {aka 'const std::any&'}
1276 | push_back(const value_type& __x)
| ~~~~~~~~~~~~~~~~~~^~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(value_type&&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1293 | push_back(value_type&& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:30: note: no known conversion for argument 1 from 'std::unique_ptr<A>' to 'std::vector<std::any>::value_type&&' {aka 'std::any&&'}
1293 | push_back(value_type&& __x)
| ~~~~~~~~~~~~~^~~
main.cpp:24:18: error: no matching function for call to 'std::vector<std::any>::push_back(std::unique_ptr<B>)'
24 | vec.push_back(std::unique_ptr<B>(b));
| ~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1276 | push_back(const value_type& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1276:35: note: no known conversion for argument 1 from 'std::unique_ptr<B>' to 'const std::vector<std::any>::value_type&' {aka 'const std::any&'}
1276 | push_back(const value_type& __x)
| ~~~~~~~~~~~~~~~~~~^~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:7: note: candidate: 'void std::vector<_Tp, _Alloc>::push_back(value_type&&) [with _Tp = std::any; _Alloc = std::allocator<std::any>; value_type = std::any]'
1293 | push_back(value_type&& __x)
| ^~~~~~~~~
/usr/local/include/c++/12.1.0/bits/stl_vector.h:1293:30: note: no known conversion for argument 1 from 'std::unique_ptr<B>' to 'std::vector<std::any>::value_type&&' {aka 'std::any&&'}
1293 | push_back(value_type&& __x)
|
Is this possible with std::any? Or is there any alternative? I am unable to use std::variant as I don't know all the types to be stored upfront.
Edit:
I just want to use the unique_ptr vector to enforce that objects will be cleaned up at program exit (the vector will be alive till program exit). The consumer code will use direct references to a and b so I don't need to access/enumerate object references via the vector.
答案1
得分: 8
std::unique_ptr
不能用作 std::any
,因为后者要求值类型是可复制构造的,而 std::unique_ptr
不是。
根据您描述的用例:
一个直接的解决方案是改用 std::shared_ptr
,因为它是可复制构造的。
然而,在这种情况下 std::any
完全不是必需的。所有 std::shared_ptr
实例都可以随时转换为 std::shared_ptr<void>
。删除器是类型擦除的,并且仍将按预期调用:
std::vector<std::shared_ptr<void>> vec;
auto new_a = std::make_shared<A>();
A* a = new_a.get();
vec.push_back(std::move(new_a));
auto new_b = std::make_shared<B>();
B* b = new_b.get();
vec.push_back(std::move(new_b));
// 在此处使用 a 和 b,假设 vec 的生存周期超出它们
元素需要直接构造为 shared_ptr
(例如,使用 std::make_shared
),因为否则在 new
表达式和 shared_ptr
构造之间发生异常将导致内存泄漏。std::move
是可选的。
然而,std::shared_ptr
比你实际需要的要多得多。std::unique_ptr
没有一个可以使用的 void
实例,因为删除器的类型没有被擦除,但您可以通过编写一个从具有虚拟析构函数的单个非模板基类派生的 std::unique_ptr
等效物来实现相同的效果。然后,您可以在您的向量中使用该基类作为元素类型。
(如果您对额外的间接性感到满意,可以通过在派生模板中直接使用 std::unique_ptr
,然后在基类中使用 std::unique_ptr<base>
来轻松实现这一点。如果您不希望使用间接性,那么我认为您将不得不从头开始实现它。我想不出一个有用的标准库功能。)
还可以实现自己的支持不可复制类型的 std::any
等效物。std::any
简单地决定支持复制,一旦做出这个决定,所有可能包含的类型必须支持它。但这比我上面建议的更复杂。
然而,更简单的解决方案是,假设对于您而言这样的设计和性能是可接受的,那就是所有您打算存储的类 A
、B
等都派生自带有虚拟析构函数的 Base
,在这种情况下,简单地 std::vector<std::unique_ptr<Base>>
也可以。在这种情况下,虚拟析构函数是必需的!否则,您将会有未定义行为!
英文:
std::unique_ptr
cannot be used as a std::any
, because the latter requires the value type to be copy-constructible, which std::unique_ptr
is not.
Given the use case that you described:
A straight-forward solution would be to use std::shared_ptr
instead, which is copy-constructible.
However, in that case std::any
is not necessary at all. All std::shared_ptr
instances can always be converted to std::shared_ptr<void>
. The deleter is type-erased and will still be called as expected:
std::vector<std::shared_ptr<void>> vec;
auto new_a = std::make_shared<A>();
A* a = new_a.get();
vec.push_back(std::move(new_a));
auto new_b = std::make_shared<B>();
B* b = new_b.get();
vec.push_back(std::move(new_b));
// use a and b here, assuming that vec outlives them
The element needs to be constructed directly into a shared_ptr
(e.g. with std::make_shared
), because otherwise an exception inbetween the new
expression and construction of the shared_ptr
will cause a memory leak. std::move
is optional.
However, std::shared_ptr
is much more than you really need. std::unique_ptr
doesn't have a void
instance that you can use because the deleter's type is not erased, but you can achieve the same effect by writing a std::unique_ptr
equivalent which derives from a single non-template base class with virtual destructor. Then you can use that base class as element type in your vector.
(If you are confortable with an additonal indirection, this can be easily implemented by using std::unique_ptr
itself in the derived template and then using std::unique_ptr<base>
in the base class. If you don't want the indirection, then I think you'll have to implement it from scratch. I can't think of a helpful standard library functionality.)
It is also possible to implement your own std::any
equivalent that supports non-copyable types. std::any
simply made the decision to support copying and once that decision is made all potentially contained types must support it. But that's more complex than what I suggested above.
However, an even easier solution, assuming that is acceptable design and performance for you, is to have all classes A
, B
, etc. that you intent to store derive from some Base
with virtual destructor, in which case simply std::vector<std::unique_ptr<Base>>
will also do it. In this case the virtual destructor is however required! Otherwise you will have undefined behavior!
答案2
得分: 3
如果您只需要销毁对象,那么可以做一些更简单的事情,例如像这样:
#include <iostream>
#include <vector>
#include <functional>
#include <utility>
class A
{
public:
~A() {std::cout << "~A()\n";}
int val;
};
class B
{
public:
~B() {std::cout << "~B()\n";}
float val;
};
class ObjKeeper
{
public:
~ObjKeeper() {
for (auto& f : objs) {
f();
}
}
template<typename T>
void addObj(T *obj) {
objs.push_back([obj]() {delete obj;});
}
private:
std::vector<std::function<void()>> objs;
};
int main()
{
ObjKeeper keep;
auto a = new A();
auto b = new B();
keep.addObj(a);
keep.addObj(b);
}
英文:
If you need it only to destroy objects, then you can do something much simpler, for example like this:
#include <iostream>
#include <vector>
#include <functional>
#include <utility>
class A
{
public:
~A() {std::cout << "~A()\n";}
int val;
};
class B
{
public:
~B() {std::cout << "~B()\n";}
float val;
};
class ObjKeeper
{
public:
~ObjKeeper() {
for (auto& f : objs) {
f();
}
}
template<typename T>
void addObj(T *obj) {
objs.push_back([obj]() {delete obj;});
}
private:
std::vector<std::function<void()>> objs;
};
int main()
{
ObjKeeper keep;
auto a = new A();
auto b = new B();
keep.addObj(a);
keep.addObj(b);
}
答案3
得分: 1
以下是翻译好的部分:
"In addition to what others wrote, I'd also say that storing a smart pointer instance in
std::vector<std::any> vec;
is an anti-pattern or a code smell.
This is because a std::any
instance, similarly to a smart pointer instance, will take over unique ownership over the object it holds, while a smart pointer will only take over the ownership of a pointer. However, if you copy a std::any
instance, unlike a std::unique_ptr
instance, it will want to copy the object it holds. Therefore, I'd just try doing this first. A std::any
instance will destroy the object it holds, so there is no need for a smart pointer in that regard.
Your application design could probably be improved, but writing a custom std::any
, that behaves differently than the standard one (you could forbid copying or implement shared ownership, for example), is a worthy project, as type erasure is a recurring topic in c++.
Lastly, you can use memory sanitizers, if you're worried about memory leaks."
英文:
In addition to what others wrote, I'd also say that storing a smart pointer instance in
std::vector<std::any> vec;
is an anti-pattern or a code smell.
This is because a std::any
instance, similarly to a smart pointer instance, will take over unique ownership over the object it holds, while a smart pointer will only take over the ownership of a pointer. However, if you copy a std::any
instance, unlike a std::unique_ptr
instance, it will want to copy the object it holds. Therefore, I'd just try doing this first. A std::any
instance will destroy the object it holds, so there is no need for a smart pointer in that regard.
Your application design could probably be improved, but writing a custom std::any
, that behaves differently than the standard one (you could forbid copying or implement shared ownership, for example), is a worthy project, as type erasure is a recurring topic in c++.
Lastly, you can use memory sanitizers, if you're worried about memory leaks.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论