简体   繁体   English

如何移动initializer_list的元素?

[英]how to move elements of an initializer_list?

Let's say you have a variable of type std::vector<std::string> and you initialize it with an initializer list: 假设您有一个std::vector<std::string>类型的变量,并使用初始化列表初始化它:

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

The compiler will create a temporary std::string for each string literal, create an initializer list over these and then call the ctor for V and create the vector. 编译器将为每个字符串文字创建一个临时的std::string ,在这些文件上创建一个初始化列表,然后调用ctor for V并创建向量。 The ctor does not know that all those strings are temporaries, so it is copying each string. ctor不知道所有这些字符串都是临时字符串,所以它正在复制每个字符串。

I haven't found anything in the standard which allows the vector ctor to move the elements when they are temporaries. 我没有在标准中找到任何允许矢量ctor在临时时移动元素的东西。

Am I missing something or does using initializer lists lead to unnecessary copies? 我错过了什么或使用初始化程序列表导致不必要的副本? I am writing classes where this problem could lead to significantly inefficient code. 我正在编写类,这个问题可能会导致代码效率低下。 Any technique to avoid unnecessary copies would be greatly appreciated. 任何避免不必要的副本的技术将不胜感激。

There is no way to avoid the copying from an initializer_list<string> , because the standard defines the invocation of a constructor taking an initializer list argument, from a curly braces initializer as actual argument, as follows (emphasis added): 没有办法避免从initializer_list<string>复制,因为标准定义了一个构造函数的调用,该构造函数采用初始化列表参数,从花括号初始化器作为实际参数,如下所示(强调添加):

C++14 §8.5.4/5 C ++14§8.5.4/ 5

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation allocated a temporary array of N elements of type const E , where N is the number of elements in the initializer list 类型为std::initializer_list<E>的对象是从初始化列表构造的,就好像实现分配了一个类型为const EN元素的临时数组,其中N是初始化列表中元素的数量

IMHO this is really unfortunate. 恕我直言,这真的很不幸。

A workaround (for your own classes) is to accept initializer_list<char const*> . 解决方法(对于您自己的类)是接受initializer_list<char const*>


Here's an example of the workaround applied to std::vector<string> . 以下是应用于std::vector<string>的变通方法的示例。 For that, where you don't control the class' code, it involves declaring a data array (actually an initializer_list ) explicitly. 为此,在不控制类的代码的情况下,它涉及明确声明数据数组(实际上是initializer_list )。 This is just as with C++03, which the initializer list mechanism was intended to avoid: 这与初始化程序列表机制旨在避免的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 ) ); } 

Output: 输出:

\nMaking vector a. 制作矢量了。\n  My_string(*) <- 'a1' My_string(*)< - 'a1'\n  My_string(*) <- 'a2' My_string(*)< - 'a2'\n  My_string(*) <- 'a3' My_string(*)< - 'a3'\n  My_string(const&) <- 'a1' My_string(const&)< - 'a1'\n  My_string(const&) <- 'a2' My_string(const&)< - 'a2'\n  My_string(const&) <- 'a3' My_string(const&)< - 'a3'\nMaking data for vector b. 为矢量b制作数据。\nMaking vector b. 制作矢量b。\n  My_string(*) <- 'b1' My_string(*)< - 'b1'\n  My_string(*) <- 'b2' My_string(*)< - 'b2'\n  My_string(*) <- 'b3' My_string(*)< - 'b3'\n

After some thinking, I came up with a solution based on mutable . 经过一番思考,我想出了一个基于mutable的解决方案。 The other answer is still mostly correct, but one can create a proxy with a mutable member to get rid of the top-level const -ness and then move the elements from there. 另一个答案仍然大多是正确的,但是可以创建一个带有可变成员的代理来摆脱顶级const -ness然后从那里移动元素。 Methods taking an initializer list should therefore overload for a const-ref initializer list and an rvalue-ref version to know when they are allowed to move. 因此,获取初始化列表的方法应该重载const-ref初始化列表和rvalue-ref版本,以了解何时允许它们移动。

Here's a working example, it might look arbitrary at first but in my real-world use-case, it solved the problem. 这是一个有效的例子,起初看起来可能是任意的,但在我的实际用例中,它解决了这个问题。

#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 实例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM