如何移动 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"} } );
}
假设您有一个 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>
进行复制,因为标准定义了构造函数的调用采用初始化列表参数,从花括号初始化器作为实际参数,如下所示(重点添加) ):
” An object of type
std::initializer_list<E>
is constructed from an initializer list as if the implementation allocated a temporary array ofN
elements of typeconst E
, whereN
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"} } );
}