c++ 如何移动initializer_list的元素?

bkkx9g8r  于 2023-05-20  发布在  其他
关注(0)|答案(3)|浏览(162)

假设你有一个std::vector<std::string>类型的变量,你用一个初始化器列表初始化它:

using V = std::vector<std::string>;
V v = { "Hello", "little", "world", "of", "move", "semantics" };

编译器将为每个字符串文字创建一个临时std::string,在这些字符串上创建一个初始化列表,然后调用V的ctor并创建向量。ctor不知道所有这些字符串都是临时的,所以它复制每个字符串。
我还没有在标准中找到任何允许向量ctor移动临时元素的东西。
是我遗漏了什么,还是使用初始化器列表会导致不必要的副本?我正在编写的类中,这个问题可能会导致代码效率低下。任何避免不必要的复制的技术都将受到极大的赞赏。

eqqqjvef

eqqqjvef1#

没有办法避免从initializer_list<string>复制,因为标准定义了一个构造函数的调用,该构造函数从一个花括号初始化器作为实际参数,接受一个初始化器列表参数,如下所示(强调添加):

C++14 §8.5.4/5
"std::initializer_list<E>类型的对象是从初始化器列表构造的,就好像实现分配了一个**const E**类型的N元素的临时数组,其中N是初始化器列表中元素的数量

IMHO这真的很不幸。
一个解决方法(对于您自己的类)是接受initializer_list<char const*>
下面是应用于std::vector<string>的解决方法的示例。为此,在不控制类代码的情况下,需要显式声明一个数据数组(实际上是一个initializer_list)。这就像C++03一样,初始化器列表机制旨在避免:

#include <vector>
#include <initializer_list>
#include <iostream>
#include <iterator>              // std::begin, std::end
using namespace std;

struct My_string
{
    char const* const ps;

    My_string( char const* const s )
        : ps( s )
    {
        cout << "  My_string(*) <- '" << s << "'" << endl;
    }

    My_string( My_string const& other )
        : ps( other.ps )
    {
        cout << "  My_string(const&) <- '" << other.ps << "'" << endl;
    };

    My_string( My_string&& other )
        : ps( other.ps )
    {
        cout << "  My_string(&&) <- '" << other.ps << "'" << endl;
    };
};

auto main() -> int
{
    cout << "Making vector a." << endl;
    vector<My_string> const a   = {"a1", "a2", "a3"};
    cout << "Making data for vector b." << endl;
    auto const b_data           = { "b1", "b2", "b3" };
    cout << "Making vector b." << endl;
    vector<My_string> const b( begin( b_data ), end( b_data ) );
}

输出:

Making vector a.
  My_string(*) <- 'a1'
  My_string(*) <- 'a2'
  My_string(*) <- 'a3'
  My_string(const&) <- 'a1'
  My_string(const&) <- 'a2'
  My_string(const&) <- 'a3'
Making data for vector b.
Making vector b.
  My_string(*) <- 'b1'
  My_string(*) <- 'b2'
  My_string(*) <- 'b3'
bpzcxfmw

bpzcxfmw2#

经过一番思考,我想出了一个基于mutable的解决方案。另一个答案基本上仍然是正确的,但是可以创建一个带有可变成员的代理,以摆脱顶层的const,然后从那里移动元素。因此,接受初始值设定项列表的方法应该重载const-ref初始值设定项列表和rvalue-ref版本,以便知道何时允许移动它们。
这里有一个工作示例,它可能看起来很随意,但在我的真实用例中,它解决了这个问题。

#include <iostream>
#include <vector>

// to show which operations are called
struct my_string
{
    const char* s_;
    my_string( const char* s ) : s_( s ) { std::cout << "my_string(const char*) " << s_ << std::endl; }
    my_string( const my_string& m ) : s_( m.s_ ) { std::cout << "my_string(const my_string&) " << s_ << std::endl; }
    my_string( my_string&& m ) noexcept : s_( m.s_ ) { std::cout << "my_string(my_string&&) " << s_ << std::endl; }
    ~my_string() { std::cout << "~my_string() " << s_ << std::endl; }
};

// the proxy
struct my_string_proxy
{
    mutable my_string s_;

    // add all ctors needed to initialize my_string
    my_string_proxy( const char* s ) : s_( s ) {}
};

// functions/methods should be overloaded
// for the initializer list versions

void insert( std::vector<my_string>& v, const std::initializer_list<my_string_proxy>& il )
{
    for( auto& e : il ) {
        v.push_back( e.s_ );
    }
}

void insert( std::vector<my_string>& v, std::initializer_list<my_string_proxy>&& il )
{
    for( auto& e : il ) {
        v.push_back( std::move( e.s_ ) );
    }
}

int main()
{
    std::vector<my_string> words;
    insert( words, { {"Hello"}, {"initializer"}, {"with"}, {"move"}, {"support"} } );
}

Live example

gijlo24d

gijlo24d3#

为了节省模板的开销,你可以不使用初始化器列表,而是使用数组的右值引用:

template<typename T, std::size_t n>
void insert(std::vector<T>& vector, T(&&elements)[n])
{
    for (T& element : elements)
        vector.push_back(std::move(element));
}

(The如果您知道类型,则模板参数T对于该技术不是必需的,但n是必需的。)

int main()
{
    std::vector<std::unique_ptr<std::string>> strings;
    // C++14 onward: use std::make_unique
    insert(strings, {
        std::unique_ptr<std::string>{ new std::string{} },
        std::unique_ptr<std::string>{ new std::string{"abc"} }
    });
}

相关问题