【问题标题】:Creating the container of smart pointers by cloning elements of another container通过克隆另一个容器的元素来创建智能指针容器
【发布时间】:2021-09-29 12:43:16
【问题描述】:

我有一个类,它支持克隆(通过方法clone)。我在std::unique_ptr 的向量中有一堆它的实例。

现在,我想从上述向量创建一个具有相同智能指针的std::set,理想情况下是在其构造过程中。明显的设计如下:

#include <memory>
#include <set>
#include <vector>

class A
{
public:
    /// type of itself
    typedef A self;
    A() = default;
    A(const self& another) = default;
    virtual ~A() = default;

    std::unique_ptr<A> clone() const
    {
        return std::make_unique<A>();
    }
};

class SetOfA
{
public:
    SetOfA() = default;

    // Here is the method I would like to improve
    SetOfA(const std::vector<std::unique_ptr<A> >& data)
    {
        //do not like this loop, prefer this to be in initialization part?
        for (const auto& d : data) {
            set_of_a.insert(std::move(d->clone()));
        }
    }

private:
    std::set<std::unique_ptr <A> > set_of_a;
};

但是有没有办法在构造函数中避免 for 循环并将 std::set 构造移动到初始化部分?

【问题讨论】:

  • 请注意,std::move() 在那里是多余的。 clone() 返回一个纯右值。
  • 我相信你运气不好,除非你把“为向量编写一个单独的克隆函数”作为解决方案。
  • C++20 std::views::transform 接近,但随后 std::set 没有构造函数来查看。
  • @aschepler 您只需要一个调用迭代器构造函数的中间函数。 template &lt;typename Container, std::range Range&gt; Container construct(Range&amp;&amp; r) { return { r.begin(), r.end() }; }

标签: c++ stdvector stdset


【解决方案1】:

如果你可以使用 Boost,这里有一个可能的解决方案:

SetOfA(const std::vector<std::unique_ptr<A>>& data) :
    set_of_a(
        boost::make_transform_iterator(data.begin(), std::mem_fn(&A::clone)),
        boost::make_transform_iterator(data.end(),   std::mem_fn(&A::clone)))
    { }

【讨论】:

  • @JeJo Move 迭代器会将向量元素留空。鉴于问题中对cloneconst 向量的讨论,我认为这里不需要。
【解决方案2】:

您可以使用std::transform 来避免for 循环在std::set 中插入值。

SetOfA(const std::vector<std::unique_ptr<A> >&data) 
{
   std::transform(data.begin(), data.end(), std::inserter(set_of_a, set_of_a.begin()), [](auto& a){return a->clone();});
 // or better
 //std::transform(data.begin(), data.end(), std::inserter(set_of_a, set_of_a.begin()), std::mem_fn(&A::clone));
}

它可以用 C++14 及更高版本编译。请在此处找到它的测试版本https://godbolt.org/z/jzvaKovn7

并且要将整个东西放在初始化部分,您可以使用 lambda 或 Functor,将数据转换为集合,如下所示:

class SetOfA
{
    struct VectorToSetConverter
    {
        std::set<std::unique_ptr<A>> operator()  (const std::vector<std::unique_ptr<A> >&data) const
        {
            std::set<std::unique_ptr<A>> set_of_a;
            std::transform(data.begin(), data.end(), std::inserter(set_of_a, set_of_a.begin()), std::mem_fn(&A::clone));
            return set_of_a;
        }
   };

  public:
  
    SetOfA() = default;
    SetOfA(const std::vector<std::unique_ptr<A> >&data)
    : set_of_a(std::move(VectorToSetConverter()(data))) // move might not be necessary
    {
    }
    
  private:

    std::set<std::unique_ptr <A> > set_of_a;
};

https://godbolt.org/z/6aGPKndb6

【讨论】:

  • 你是对的@JeJo。我刚刚更新了我的答案。
  • 确实如此。如果数据 SetOfA 构造函数是按值传递的,std::make_move_iterator 可能是一个更好的选择。
猜你喜欢
  • 2014-03-04
  • 2011-06-14
  • 2019-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 2011-07-25
  • 2018-05-11
相关资源
最近更新 更多