我们知道 std::move实际上并没有移动任何东西。它只是将左值引用 (&) 转换为右值引用 (&&)。
那么在下面的例子中,复制构造函数是如何调用的呢?如果没有移动构造函数,那么构造使用 std::move() 的对象如何依靠复制构造函数?变量的绑定究竟是如何b发生的?
struct Test {
// Default constructor
Test() {
std::cout << "Constructor is called." << std::endl;
mValue = 0;
}
// Copy constructor
Test(const Test& rhs) {
std::cout << "Copy Constructor is called." << std::endl;
mName = rhs.mName;
mValue = rhs.mValue;
}
std::string mName;
int mValue;
};
int main() {
Test a;
Test b = std::move(a);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
Constructor is called.
Copy Constructor is called.
Run Code Online (Sandbox Code Playgroud) 我试图理解移动语义的通用规则。特别是容器和包含的元素。
原因是我试图在所有权和迭代器失效的背景下理解移动。为此,我将经历一些复杂性不断增加的案例,涉及典型容器、通用包含类型T、通用g和f函数。(也许一个重要的额外细节是,f实际上可能会或可能不会执行移动操作,或者它可能在运行时是偶然的。)
这个想法是引入案例3,这是这个问题的核心。
首先是一个相当没有争议的案例,这是可以的:
std::vector<T> v(100, t);
f(std::move(v));
v = make_a_vector();
Run Code Online (Sandbox Code Playgroud)
然而,移动后使用可能是臭代码
std::vector<T> v(100, t);
f(std::move(v));
g(v);
Run Code Online (Sandbox Code Playgroud)
我想大多数人都同意上面的做法是不行的。规则是(据我所知)移动后的唯一操作应该是赋值。我认为这尤其是因为它没有记录(未定义但有效的状态)移出向量的状态是什么(或者即使它被移动了)。因此,充其量v是空的,最坏的v情况是未指定的状态,因此g可以在此范围内执行未指定的操作。
std::vector<T> v(100, t);
f(std::move(v));
v.resize(120);
Run Code Online (Sandbox Code Playgroud)
这是对的吗?这不是一项任务,但resize没有先决条件。(发现这个Can I resize a vector that was moving from?)
现在是真正棘手的情况。
std::vector<T> v(100);
h(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()));
v.resize(120);
Run Code Online (Sandbox Code Playgroud)
(这里,h是一个采用迭代器的函数,假设它隐式引用 range 。 [iterator1, iterator2))
这是正确的代码吗?原因是.resize似乎要播放、移动、交换和复制类型为移出的对象T。
总而言之,调整其元素已(可能)移出的向量的大小是否正确?
编辑:为了论证,让我们指定函数的签名,以防它们与答案相关:
template<class …Run Code Online (Sandbox Code Playgroud) 我用以下代码编写了一个类:
class Test {
public:
...
Test( const Test &&that ) : i(that.i), s(std::move(that.s)) {
cout << "move contructor." << endl;
}
...
private:
int i;
std::string s;
};
Run Code Online (Sandbox Code Playgroud)
如果我反汇编生成的代码,我会看到:
.type Test::Test(Test const&&), @function
Test::Test(Test const&&):
...
call std::remove_reference<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>::type&& std::move<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
movq %rax, %rsi
movq %rbx, %rdi
.LEHB3:
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@PLT
Run Code Online (Sandbox Code Playgroud)
它的调用让我感到惊讶,basic_string<...>::basic_string( basic_string<...> const&)因为我期望调用 basic_string 的移动构造函数basic_string<...>::basic_string( basic_string<...> &&)。
我以错误的方式实现了移动构造函数?
是否可以T从 a 创建堆栈变量(具有移动构造函数的类型)std::unique_ptr<T>?
我尝试过类似的东西
std::unique_ptr<T> p = ext_get_my_pointer(); // external call returns a smart pointer
T val{std::move(*p.release())}; // I actually need a stack variable
Run Code Online (Sandbox Code Playgroud)
但它看起来很难看,并且显然会造成内存泄漏。但不知道为什么。
通过在PVS-Studio中进行一些代码分析,它给了我一些警告消息。
我在头文件中有以下语句:
constexpr int MIN_ALLOWED_Y { 0 };
Run Code Online (Sandbox Code Playgroud)
在源文件中:
std::make_pair<const int, const int>( std::move( MIN_ALLOWED_Y ), std::move( MAX_ALLOWED_Y ) )
Run Code Online (Sandbox Code Playgroud)
在上面的表达式中,我曾经std::move转换MIN_ALLOWED_Y为 xvalue,因为我认为std::make_pair只接受右值;
// from https://en.cppreference.com/w/cpp/utility/pair/make_pair
template< class T1, class T2 >
constexpr std::pair<V1,V2> make_pair( T1&& t, T2&& u );
Run Code Online (Sandbox Code Playgroud)
但我收到如下警告消息:
V833 Passing the const-qualified object 'MIN_ALLOWED_Y' to the 'std::move' function disables move semantics.
Run Code Online (Sandbox Code Playgroud)
这是有效的警告吗?如果是这样那我该怎么办?我应该删除std::move(也许在这种情况下它是多余的?)?
更好的问题是哪里不应该使用std::move?
#include <memory>
#include <functional>
#include <iostream>
struct TestStruct {
int a = 100;
};
struct StructDeleter {
void operator()(TestStruct *ptr) const {
delete ptr;
}
};
std::unique_ptr<TestStruct, StructDeleter> MakeNewStruct() {
std::unique_ptr<TestStruct> st(new TestStruct());
std::unique_ptr<TestStruct, StructDeleter> customDeleterSt(std::move(st));
std::cout << customDeleterSt->a << std::endl;
return customDeleterSt;
}
int main() {
auto a = MakeNewStruct();
std::cout << a->a << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码无法编译,如何st移至customDeleterSt?我st从创建界面获得了一个 unique_ptr st,并使用自定义删除器对用户隐藏了 的实现st,那么如何将没有自定义删除器的 unique_ptr 移动到具有自定义删除器的 unique_tr 呢?
谢谢你的帮助!
我一直在玩c ++ 11移动语义
在代码中......
#include <vector>
#include <string>
std::vector<std::string> GetNewVector()
{
std::vector<std::string> newVec;
newVec.push_back(std::string("hello")); //(1)
newVec.push_back(std::string("whey")); //(2)
return newVec;
}
int main(int argc, char* argv[])
{
std::vector<std::string> vec = GetNewVector();
}
Run Code Online (Sandbox Code Playgroud)
在点(1),当对象移动到向量中时,调用"hello"对象的移动构造函数.
在第(2)点,首先再次调用"hello"的移动构造函数,(我假设这是向量重新分配的位置),然后调用"whey"移动构造函数.
这完全符合预期,但是我期望在结束时返回向量时再次移动对象GetNewVector(),但是移动构造函数不会再次被调用.我的猜测是RVO正在发生,但是当我在调试模式下运行Visual Studio(2k10)时,我不确定是否会发生这种情况?
如果可以执行RVO,那么它将优先于使用移动构造函数吗?
我正在看这个显示移动构造函数的答案:
#include <cstring>
#include <algorithm>
class string
{
char* data;
public:
string(const char* p)
{
size_t size = strlen(p) + 1;
data = new char[size];
memcpy(data, p, size);
}
~string()
{
delete[] data;
}
string(const string& that)
{
size_t size = strlen(that.data) + 1;
data = new char[size];
memcpy(data, that.data, size);
}
};
Run Code Online (Sandbox Code Playgroud)
然后介绍一个移动构造函数:
string(string&& that) // string&& is an rvalue reference to a string
{
data = that.data;
that.data = 0;
}
Run Code Online (Sandbox Code Playgroud)
如果我们分配指向data该值的指针that.data,肯定这会导致内存泄漏,该new …
在这个例子中,移动语义是如何工作的:
struct test {
int ii[10];
int i;
};
test f() {
test a;
std::cout << "[1] " << &a << std::endl;
return a;
}
int main()
{
test x (f());
std::cout << "[2] " << &x << std::endl;
test x1 (std::move(x));
std::cout << "[3] " << &x1;
}
Run Code Online (Sandbox Code Playgroud)
输出:
[1] 0x7fff50ac70c0
[2] 0x7fff50ac70c0
[3] 0x7fff50ac70f0
Run Code Online (Sandbox Code Playgroud)
为什么x是使用f()的返回值构造的,但是x1的地址与x不同?
编辑
struct test {
std::string s;
}
[...]
std::cout << (*void)&x.s[0];
Run Code Online (Sandbox Code Playgroud)
我想最终我明白了.现在地址是一样的.
假设我有一个元组和一个函数:
typedef std::tuple< std::unqiue_ptr<int>, std::unqiue_ptr<char> > SomeTuple;
void someFunction( std::unqiue_ptr<int>, std::unqiue_ptr<char> );
Run Code Online (Sandbox Code Playgroud)
所以在辅助函数中我将元组展开为参数:
void unroll( SomeTuple &t )
{
someFunction( std::get<0>( std::move( t ) ), std::get<1>( std::move( t ) ) );
}
Run Code Online (Sandbox Code Playgroud)
它有效,但我想避免重复std::move多次.天真的解决方案如:
void unroll( SomeTuple &t )
{
auto &&rt = std::move( t );
someFunction( std::get<0>( rt ), std::get<1>( rt ) );
}
Run Code Online (Sandbox Code Playgroud)
显然不起作用,因为rt是一个lvalue.那么有没有办法避免std::move()每次重复多次std::get?
c++ ×10
move-semantics ×10
c++11 ×5
unique-ptr ×2
pointers ×1
pvs-studio ×1
stdtuple ×1
stdvector ×1
vector ×1