Reputation: 1564
I am trying to hold a variant of pointers to templated versions of a base class in a vector. The boost::variant
of pointers happens to be contained in a struct. It works fine if these pointers are raw pointers, but things start going wrong when I change them to unique_ptr
.
struct Sayer {
struct Category {
using GetterVariant = boost::variant<
//Getter<string>*, // works OK
//Getter<double>*, // ...
//Getter<int>* // ...
unique_ptr<Getter<string>>,
unique_ptr<Getter<double>>,
unique_ptr<Getter<int>>
>;
Category(GetterVariant g) :
getter(g)
{}
GetterVariant getter;
};
vector<Category> categories;
template <typename G>
void addGetter() {
categories.emplace_back(new G()); // allocate here, transfer ownership to Sayer::categories
}
};
Compiler error:
/usr/include/boost/variant/variant.hpp:1627:28: error: no matching member
function for call to 'initialize'
initializer::initialize(
~~~~~~~~~~~~~^~~~~~~~~~
/usr/include/boost/variant/variant.hpp:1798:9: note: in instantiation of function
template specialization
'boost::variant<std::unique_ptr<Getter<std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char> > >,
std::default_delete<Getter<std::__cxx11::basic_string<char,
std::char_traits<char>, std::allocator<char> > > > >,
std::unique_ptr<Getter<double>, std::default_delete<Getter<double> > >,
std::unique_ptr<Getter<int>, std::default_delete<Getter<int> > >
>::convert_construct<AgeGetter *>' requested here
convert_construct( detail::variant::move(operand), 1L);
...
main.cpp:54:16: note: in instantiation of function template specialization
'std::vector<Sayer::Category, std::allocator<Sayer::Category>
>::emplace_back<AgeGetter *>' requested here
categories.emplace_back(new G());
^
main.cpp:65:9: note: in instantiation of function template specialization
'Sayer::addGetter<AgeGetter>' requested here
sayer.addGetter<AgeGetter>();
...
/usr/include/boost/variant/detail/initializer.hpp:115:24: note: candidate
function not viable: no known conversion from 'typename
::boost::move_detail::remove_reference<AgeGetter *&>::type'
(aka 'AgeGetter *') to 'std::unique_ptr<Getter<int>,
std::default_delete<Getter<int> > >' for 2nd argument
/usr/include/boost/variant/detail/initializer.hpp:149:17: note: candidate
function not viable: requires 0 arguments, but 2 were provided
static void initialize();
How do I set this up so that the memory ownership is in the container?
Upvotes: 0
Views: 186
Reputation: 5095
Two things:
First, you have to move g
in the Category
Constructor, since a variant is non-copyable if any of its members is non-copyable.
Second, while every conversion in the chain AgeGetter*
to Getter<int>*
to std::unique_ptr<Getter<int>>
to Category
is implicit, C++ only does a limited number of implicit conversions. So basically this chain is too long and you can fix it for example by using emplace_back(std::make_unique<G>())
instead of emplace_back(new G())
.
Also, this is safer, since if emplace_back
throws (which it can), the new G()
would not be deleted and hence leak. But the destructor unique_ptr
returned by std::make_unique<G>()
would be called if emplace_back
throws and hence there would be no leak. You should always try to avoid raw new
in your code.
Upvotes: 3