c++ 使用std::vector< std::any>存储异构unique_ptrs

de90aj5v  于 2023-02-20  发布在  其他
关注(0)|答案(3)|浏览(238)

我想在一个向量中存储不同类型的unique_ptrs。
我尝试使用std::any,如下所示。

#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));
}

其失败原因如下。

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)
      |

这可以用std::any实现吗?或者有其他的选择吗?我不能用std::variant,因为我不知道所有要预先存储的类型。

编辑

我只想使用unique_ptr向量来强制对象在程序退出时被清除(向量在程序退出前一直有效),消费者代码将使用对a和b的直接引用,因此我不需要通过向量访问/枚举对象引用。

wn9m85ua

wn9m85ua1#

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));

// use a and b here, assuming that vec outlives them

需要将元素直接构造为shared_ptr(例如,使用std::make_shared),否则new表达式和shared_ptr构造之间的异常将导致内存泄漏。std::move是可选的。
然而,std::shared_ptr比你真正需要的要多得多。std::unique_ptr没有一个void示例可以使用,因为删除器的类型没有被擦除,但是你可以通过写一个std::unique_ptr等价物来达到同样的效果,这个等价物是从一个带有虚析构函数的非模板基类派生出来的,然后你可以把那个基类作为你的向量中的元素类型。
(If您可以使用额外的间接寻址,这可以通过在派生模板中使用std::unique_ptr本身,然后在基类中使用std::unique_ptr<base>来轻松实现。如果您不需要间接寻址,那么我认为您必须从头开始实现它。我想不出有什么有用的标准库功能。)
你也可以实现你自己的std::any等价物来支持不可复制的类型。std::any只是简单地决定支持复制,一旦决定了,所有可能包含的类型都必须支持它。但是这比我上面建议的要复杂得多。
然而,一个更简单的解决方案,假设设计和性能对你来说是可接受的,就是让你想要存储的所有类AB等等都是用虚析构函数从某个Base派生出来的,在这种情况下,std::vector<std::unique_ptr<Base>>也会这样做。但是在这种情况下,虚析构函数是必需的!否则你将有未定义的行为!

pb3s4cty

pb3s4cty2#

如果你只需要它来销毁对象,那么你可以做一些简单得多的事情,例如:

#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);
}
wb1gzix0

wb1gzix03#

除了其他人写的,我还要说在

std::vector<std::any> vec;

是反模式或代码味道。
这是因为std::any示例与智能指针示例类似,将接管其所持有的对象的唯一所有权,而智能指针仅接管指针的所有权。但是,如果复制std::any示例,则与std::unique_ptr示例不同,它将希望复制其所持有的对象。因此,我只是先尝试执行this,一个std::any示例将执行destroy the object it holds,所以在这方面不需要智能指针。
您的应用程序设计可能会得到改进,但是编写一个与标准std::any行为不同的定制std::any(例如,您可以禁止复制或实现共享所有权)是一个值得的项目,因为类型擦除是c++中反复出现的主题。
最后,如果担心内存泄漏,可以使用memory sanitizers

相关问题