代码之家  ›  专栏  ›  技术社区  ›  vukung

std::vector的有效直接初始化

  •  3
  • vukung  · 技术社区  · 9 年前

    我有一个结构,比如

    struct A {
      A(int n) : n(n) {}
      int n;
    };
    

    我想初始化 std::vector 有一些元素。我可以通过使用初始化列表或放置新元素来完成此操作:

    // 1st version: 3 ctors, 3 copy ctors, 3 dtors                                           
    std::vector<A> v1{1, 2, 3};
    
    // 2nd version: 3 ctors                                                                  
    std::vector<A> v2;
    v2.reserve(3);
    v2.emplace_back(4);
    v2.emplace_back(5);
    v2.emplace_back(6);
    

    如注释所示,第一个版本调用了3个构造函数、3个复制构造函数和3个析构函数。带有模板的版本仅使用3个构造函数。

    问题 :显然第二个版本更好,但第一个版本更简洁。我能两全其美吗?我可以在没有额外费用的情况下直接初始化吗?

    (这是 longer version A 显示正在发生的情况的结构。)

    2 回复  |  直到 9 年前
        1
  •  5
  •   ecatmur    9 年前

    自从 A 可从转换 int ,可以使用的范围构造函数 vector :

    auto inits = {1, 2, 3};
    std::vector<A> v1{std::begin(inits), std::end(inits)};
    

    或者在单个声明语句中(假设您可以依赖RVO):

    auto v1 = [inits={1, 2, 3}] { return std::vector<A>{std::begin(inits), std::end(inits)}; }();
    
        2
  •  2
  •   Aaron McDaid    8 年前

    扩展@ecatmur的答案,我开发了一段代码,可以为任何类型的向量和任何构造函数调用提供非常通用的解决方案。矢量的每个元素的构造函数参数都存储在 tuple (第页,共页) & && 适当时),然后在构建元素时完美转发。每个元素只构造一次,本质上等同于 emplace_back 这种转发甚至允许构建仅移动类型的向量,例如 unique_ptr<?> .

    ( 使现代化 ,由于RVO,应简单地将其建造到位。然而,不幸的是,元素类型至少需要一个复制构造函数或移动构造函数才能可见,即使优化器实际上跳过了它们。这意味着您可以构建 unique_ptr ,但不是 mutex .)

    auto v2 = make_vector_efficiently<A>(
             pack_for_later(1)        // 1-arg constructor of A
            ,pack_for_later(2,"two")  // 2-arg constructor of A
            ,pack_for_later(3)        // 1-arg constructor of A
            );
    

    上述代码将创建 vector<A> 具有三个元素。在我的示例中, A 有两个构造函数,其中一个 int,string 作为参数。

    pack_for_later 生成 元组 将其参数存储为 & / && 参考文献。然后转换为对象(类型 UniquePointerThatConverts ,在本例中具有所需的转换运算符 operator A() .

    在内部 make_vector_efficiently ,生成这些转换器对象的初始值设定项列表,然后 vector 使用 begin() end() initializer_list的值。您可能希望这些迭代器必须具有类型 T* 为了构造 vector<T> ,但迭代器指向的类型可以 转换 T .

    然后,构造函数使用放置 new 从转换的对象复制到(copy-)构造。但是,感谢RVO,复制不会发生,转换器将有效地执行与 emplace_back 对我们来说。

    无论如何,任何反馈都很感谢。最后,将其扩展到除 矢量 .


    Full code on Coliru 应适用于任何C++11编译器。


    一些更详细的注释和重要功能的副本:

    pack_for_稍后 简单地构建 std::tuple .标准 make_tuple 不够好,因为它忽略了引用。由生成的元组的每个元素 pack_for_稍后 是一个参考( & && 视情况而定,取决于原始参数是左值还是右值)

    template<typename ...T> 
    std:: tuple<T&&...> pack_for_later(T&&... args) { 
            // this function is really just a more
            // 'honest' make_tuple - i.e. without any decay
        return std:: tuple<T&&...> (std::forward<T>(args)...);
    } 
    

    下一个 高效制造 是将所有人聚集在一起的功能。它的第一个参数是“Target”类型,即我们希望创建的向量中元素的类型。的集合 tuples 转换为我们的特殊转换器类型 UniquePointerThatConverts<Target> 并且如上所述构建向量。

    template<typename Target, typename ...PackOfTuples>
    auto make_vector_efficiently(PackOfTuples&&... args)
        -> std::vector<Target>
    {
        auto inits = { UniquePointerThatConverts<Target>(std::forward<PackOfTuples>(args))...};
        return std::vector<Target> {std::begin(inits), std::end(inits)};
    }
    

    因为 A. 可以有多个构造函数,我们希望能够使用它们中的任意一个, pack_for_稍后 可以返回许多不同的类型(不要忘记lvalues和rvalues)。但是我们需要一种类型来构建init列表。因此,我们定义了一个合适的接口:

    template<typename Target>
    struct ConvInterface {
        virtual Target convert_to_target_type() const = 0;
        virtual ~ConvInterface() {}
    };
    

    因此,每个元组通过以下方式转换为实现此接口的对象: make_Conv_from_tuple 。它实际上返回一个 唯一_ptr 然后将其存储在 转换的唯一指针 它具有实际的转换运算符。正是这种类型存储在init列表中,用于初始化向量。

    template<typename Target>
    struct UniquePointerThatConverts {
        std:: unique_ptr<ConvInterface<Target>> p; // A pointer to an object
                   // that implements the desired interface, i.e.
                   // something that can convert to the desired
                   // type (Target).
    
        template<typename Tuple>
        UniquePointerThatConverts(Tuple&& p_)
        : p ( make_Conv_from_tuple<Target>(std:: move(p_)) )
        {
            //cout << __PRETTY_FUNCTION__ << endl;
        }
        operator Target () const {
            return p->convert_to_target_type();
        }
    };
    

    当然,还有从包中构造的实际转换运算符。

    template<typename Target, typename ...T>
    struct Conv : public ConvInterface<Target> {
        std::tuple<T...> the_pack_of_constructor_args;
        Conv(std::tuple<T...> &&t) : the_pack_of_constructor_args(std:: move(t)) {}
    
        Target convert_to_target_type () const override {
            using idx = typename make_my_index_sequence<sizeof...(T)> :: type;
            return foo(idx{});
        }
        template<size_t ...i>
        Target foo(my_index_sequence<i...>) const {
            // This next line is the main line, constructs
            // something of the Target type (see the return
            // type here) by expanding the tuple.
            return {
                    std:: forward
                        < typename std:: tuple_element < i , std::tuple<T...> > :: type >
                        (std:: get<i>(the_pack_of_constructor_args))
                ...
            };
        }
    };