如何移动 initializer_list 的元素?

how to move elements of an initializer_list?

假设您有一个 std::vector<std::string> 类型的变量,您使用初始化列表对其进行了初始化:

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

编译器将为每个字符串文字创建一个临时的 std::string,在其上创建一个初始化列表,然后调用 V 的构造函数并创建向量。 ctor 不知道所有这些字符串都是临时字符串,因此它复制每个字符串。

我在标准中没有发现任何允许 vector ctor 在临时元素时移动元素的内容。

我是不是遗漏了什么或者使用初始化列表会导致不必要的复制?我正在写 类,这个问题可能会导致代码效率低下。任何避免不必要的复制的技术将不胜感激。

无法避免从 initializer_list<string> 进行复制,因为标准定义了构造函数的调用采用初始化列表参数,从花括号初始化器作为实际参数,如下所示(重点添加) ):

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

恕我直言,这真是不幸。

一种解决方法(适用于您自己的 classes)是接受 initializer_list<char const*>.


以下是适用于 std::vector<string> 的解决方法示例。为此,在您不控制 class' 代码的情况下,它涉及显式声明数据数组(实际上是 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'

经过一番思考,我想出了一个基于mutable的解决方案。另一个答案仍然大部分是正确的,但是可以创建一个具有可变成员的代理来摆脱顶级 const-ness 然后从那里移动元素。因此,采用初始化列表的方法应该重载 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