- 主题:c++真是快被标准委员会玩成花了啊
拷贝、移动,这在Rust里也有,因为这玩意儿决定了效率,才要搞这个区分,把决定权交给码农,码农知道什么情况下该拷贝,什么情况下该移动。懒的码农,其实可以无脑用shared_ptr的,哈哈,当然,出现内存泄漏啥的就别怪了。
std::forward那个跟模板参数推导有关,不用模板的话,用不着。
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
这几个跟内存模型无关,copy/move是性能考虑,forward纯粹是模板推导的需要。
跟内存模型有关的主要是锁、无锁。
【 在 littleSram 的大作中提到: 】
: 是不是得理解内存模型才能理解这几个概念
--
FROM 125.35.123.*
非也
【 在 littleSram 的大作中提到: 】
: 我表达有误,是不是和栈结构以及寄存器有关
: 就像go语言一样,以前用栈传参,现在新版本改成用寄存器传参
: :
--
FROM 125.35.123.*
copy/move涉及的是对象内部的数据/状态的复制、转移操作,跟对象的所有权不是一回事。
比如:
对象A内部分配了一个内存缓冲区保存在其成员m_ptr里:
A.m_ptr = new char[12];
对象B如果来copy对象A的话,B内部的m_ptr指向的是一个新分配的内存,而且两个缓冲区内的数据用memcpy搞得完全一样:
B.m_ptr = new char[12];
memcpy(B.m_ptr, A.m_ptr, 12);
对象B如果要由A move得来的话,A内部的缓冲区会转移给B,然后A内部只剩下个空指针:
B.m_ptr = A.m_ptr
A.m_ptr = nullptr;
对比一下上面的copy构造、move构造操作,就知道copy和move哪个操作效率高了。当然前提是在能move的场景下。
用 B{A}; 来声明B的意思就是告诉编译器调用copy ctor
而用 B{std::move(A)}; 来声明B的意思就是告诉编译器调用move ctor。决定权在码农。
【 在 littleSram 的大作中提到: 】
: 不需要到这一层
: 是不是理解了对象所有权就够了
: :
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
move是对象内部的资源/状态的转移(通常认为资源的复制是高开销操作,内存/文件句柄/socket句柄等都是资源)。
老对象被掏空了,废弃了,所以也有人把这个说成是对象的所有权的转移。可以不纠结字面说法。
用指针的话,编译阶段不好对对象进行跟踪,只能码农自己纯手动进行指针的跟踪,或者无脑用shared_ptr(下策)
【 在 toutouqi 的大作中提到: 】
: 可不可以这样理解,move就是不保证输入参数数据完整性的拷贝,目的是为了某些情况下减少内存拷贝?
: 用move的场景,如果改用指针,对象指针直接赋值,再把原指针赋0,连普通成员变量的拷贝都省了,看起来似乎比用move概念更清楚。不知道啥场景必须设计成用move而不用指针?
--
FROM 125.35.123.*
举一个场景:
有一个结构,我们要parse一些json数据,用parse出来的数据填充这个结构的各个字段,
然后把这个结构插入到表里(map/set/vector里)
struct MyStruct {
m1;
m2;
m3;
};
for(...) {
MyStruct temp;
temp.m1 = ParseJson1();
temp.m2 = ParseJson2();
temp.m3 = ParseJson3();
myTable.emplace_back(std::move(temp));
}
临时变量temp每次循环完都是废弃的,temp里的资源直接move走完事。
当然,也可以用裸指针
for(...) {
auto temp = new MyStruct;
temp->m1 = ParseJson1();
temp->m2 = ParseJson2();
temp->m3 = ParseJson3();
myTable.emplace_back(temp);
}
不过,最后还不能忘了myTable中的每个指针要释放掉。
for(auto &p : myTable) {
delete p;
}
还可以用std::unique_ptr,不过最后也是要执行move:
for(...) {
auto temp = std::make_unique<MyStruct>();
temp->m1 = ParseJson1();
temp->m2 = ParseJson2();
temp->m3 = ParseJson3();
myTable.emplace_back(std::move(temp));
}
我倾向于第一种写法。
【 在 toutouqi 的大作中提到: 】
: 可不可以这样理解,move就是不保证输入参数数据完整性的拷贝,目的是为了某些情况下减少内存拷贝?
: 用move的场景,如果改用指针,对象指针直接赋值,再把原指针赋0,连普通成员变量的拷贝都省了,看起来似乎比用move概念更清楚。不知道啥场景必须设计成用move而不用指针?
--
FROM 125.35.123.*
第二种是裸指针,全手动管理内存,也容易搞出异常时不安全的代码,应该一般是不推荐的。
第一种是不会存临时变量地址到表里的,不然那个写法就是错误的,不能用。
【 在 toutouqi 的大作中提到: 】
: 我习惯第二种,虽然指针有需要删除的麻烦。第一种,move会让编译器直接把临时变量的地址存到容器(比如vector)里,还是也得造个对象然后调用move拷贝函数进行成员变量的拷贝?另外,第一种如果对容器vector初始化(比如10个元素),相当于得调用10遍元素类的构造函数,而指针就没有这个问题。
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
那是move(可以认为是最浅的拷贝),不是copy
【 在 toutouqi 的大作中提到: 】
: 如果不是地址直接给容器,看起来第一种很多情况下也不会快啊,该拷贝还得拷贝。
: :
--
FROM 125.35.123.*
forward也有用,用一种简单的方式保证模板参数在函数间传递时“左值依然是左值,右值依然是右值”,
不然传递的有N个参数的话玩起来就要搞2^N种偏特化才能达到这个效果。
不搞模板则无此烦恼
forward的实现也是一个T &&的cast。这个帖子解释得比较清楚:
https://stackoverflow.com/questions/3582001/what-are-the-main-purposes-of-using-stdforward-and-which-problems-it-solves
没有std::forward,那么std::make_unique<T>(a, b, c, ...)这种就难搞,
因为make_unique函数要把参数a, b, c, ... 转发给T的ctor,
同时还要保证各个参数的值类别不变(左值依然是左值,右值依然是右值)。
也就是说,天天用的std::make_unique、std::make_shared的内部就是std::forward的用武之地。
【 在 allegro 的大作中提到: 】
: 的确,move是相当重要的,forward这个就很迷。
:
--
修改:z16166 FROM 125.35.123.*
FROM 125.35.123.*
m1/m2/m3是资源,比如堆上的符串之类的
【 在 JFly 的大作中提到: 】
:
: m1, m2, m3 如果是 POD 的话,move 无效。如果是指针的话,move 其实没意义。
: 如果是复杂数据结构,还需要确保这个类型递归实现了所有的 move 构造函数(即使这样,还是会存在有 POD 成员需要拷贝)
: ...................
--
FROM 125.35.123.*