Как изменить необработанный указатель на unique_ptr в контейнере указателей, обернутых boost::variant

#c #boost #smart-pointers #boost-variant

#c #boost #интеллектуальные указатели #boost-variant

Вопрос:

Пример живого кода

Я пытаюсь сохранить вариант указателей на шаблонные версии базового класса в векторе. boost::variant Оказывается, что указатели содержатся в структуре. Он отлично работает, если эти указатели являются необработанными указателями, но все начинает идти не так, когда я меняю их на 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
  }

};
  

Ошибка компилятора:

 /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 *amp;>::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();
  

Как мне настроить это так, чтобы владение памятью находилось в контейнере?

Комментарии:

1. Вот MWE с уменьшенным контекстом, но, надеюсь, облегчает обнаружение проблем: repl.it/repls/GigaBrokenSolution#main.cpp Спасибо!

Ответ №1:

Две вещи:

Во-первых, вам нужно переместить g Category конструктор, поскольку вариант не копируется, если какой-либо из его членов не копируется.

Во-вторых, хотя каждое преобразование в цепочке AgeGetter* to Getter<int>* to std::unique_ptr<Getter<int>> to Category является неявным, C выполняет только ограниченное количество неявных преобразований. Итак, в основном эта цепочка слишком длинная, и вы можете исправить это, например, используя emplace_back(std::make_unique<G>()) вместо emplace_back(new G()) .

Кроме того, это безопаснее, поскольку, если emplace_back throws (что он может), new G() он не будет удален и, следовательно, утечет. Но деструктор unique_ptr , возвращаемый с помощью std::make_unique<G>() , будет вызван, если emplace_back throws и, следовательно, утечки не будет. Вы всегда должны стараться избегать raw new в своем коде.

Комментарии:

1. Спасибо за объяснение, теперь имеет смысл. repl.it/repls/OvalAmpleEditors#main.cpp