我最近意识到在C++ 11中添加移动语义(或者至少我的实现,Visual C++)已经积极地(并且非常显着地)打破了我的一个优化.
请考虑以下代码:
#include <vector>
int main()
{
typedef std::vector<std::vector<int> > LookupTable;
LookupTable values(100); // make a new table
values[0].push_back(1); // populate some entries
// Now clear the table but keep its buffers allocated for later use
values = LookupTable(values.size());
return values[0].capacity();
}
Run Code Online (Sandbox Code Playgroud)
我遵循这种模式来执行容器回收:我将重新使用相同的容器而不是销毁和重新创建它,以避免不必要的堆释放和(立即)重新分配.
在C++ 03上,这很好用 - 这意味着这段代码用于返回1,因为向量是按元素复制的,而它们的底层缓冲区保持原样.因此,我可以修改每个内部向量,因为它知道它可以使用与之前相同的缓冲区.
然而,在C++ 11上,我注意到这会导致右侧移动到左侧,这对左侧的每个向量执行元素移动分配.这反过来导致向量丢弃其旧缓冲区,突然将其容量减少到零.因此,由于堆分配/释放过多,我的应用程序现在会大大减慢速度.
我的问题是:这种行为是一个错误,还是故意的?是否甚至由标准指定?
我刚刚意识到这种特定行为的正确性可能取决于是否a = A()可以使指向元素的迭代器无效a.但是,我不知道移动赋值的迭代器失效规则是什么,所以如果你知道它们,你的答案中可能值得一提.
一个unique_ptr不能被推回到一个std::vector,因为它是不可复制的,除非std::move被使用。但是,让我们F使用一个返回a的函数unique_ptr,然后std::vector::push_back(F())允许该操作。下面是一个示例:
#include <iostream>
#include <vector>
#include <memory>
class A {
public:
int f() { return _f + 10; }
private:
int _f = 20;
};
std::unique_ptr<A> create() { return std::unique_ptr<A>(new A); }
int main() {
std::unique_ptr<A> p1(new A());
std::vector< std::unique_ptr<A> > v;
v.push_back(p1); // (1) This fails, should use std::move
v.push_back(create()); // (2) This doesn't fail, should use std::move?
return 0;
}
Run Code Online (Sandbox Code Playgroud)
(2)允许,但(1)不允许。这是因为返回的值被隐式地移动了吗?
在中(2) …
在C++ 11中,值参数(和其他值)在返回时享受隐式移动:
A func(A a) {
return a; // uses A::A(A&&) if it exists
}
Run Code Online (Sandbox Code Playgroud)
至少在MSVC 2010中,右值参考参数需要std::move:
A func(A && a) {
return a; // uses A::A(A const&) even if A::A(A&&) exists
}
Run Code Online (Sandbox Code Playgroud)
我认为内部函数,右值引用和值的行为类似,唯一的区别是在值的情况下,函数本身负责销毁,而对于右值引用,责任在外面.
在标准中对待它们的动机是什么?
请考虑以下代码段:
class X;
void MoveAppend(vector<X>& src, vector<X>& dst) {
dst.reserve(dst.size() + src.size());
for (const X& x : src) dst.push_back(x);
src.clear();
}
Run Code Online (Sandbox Code Playgroud)
如果我们假设class X实现移动语义,我该如何有效地实现MoveAppend?
C++ 11中自动生成特殊移动函数(构造函数和赋值运算符)的规则指定不能声明析构函数.据推测,如果你需要在破坏中做一些特别的事情,那么这一举动可能并不安全.
但是,对于多态中正确的析构函数调用,必须将基类的析构函数声明为虚拟(否则通过其基类的指针删除子类的实例将无法正确链接析构函数).
我假设,即使是一个空的析构函数也会阻止编译器自动生成一个特殊的移动函数.如:
class Base {
virtual ~Base() { }
};
Run Code Online (Sandbox Code Playgroud)
但是,您可以默认析构函数,如下所示:
class Base {
virtual ~Base() = default;
}
Run Code Online (Sandbox Code Playgroud)
问题1:这是否允许编译器自动生成特殊的移动函数?
但是,显式默认析构函数存在问题.至少在GCC 4.8.2的情况下,签名被隐式地改为noexcept.如:
class Base {
virtual ~Base() = default; // compiler changes to:
// virtual ~Base() noexcept;
}
Run Code Online (Sandbox Code Playgroud)
虽然我在析构函数中使用noexcept没有问题,但这会破坏以下"客户端"代码:
class Sub : public Base {
virtual ~Sub(); // this declaration is now "looser" because of no noexcept
}
Run Code Online (Sandbox Code Playgroud)
所以问题2更重要的是:有没有办法允许在C++ 11中自动生成特殊的移动函数,并允许正确的析构函数链接到子类(如上所述),所有这些都不会破坏子类("客户端")代码?
在Rust中,有两种可能性来引用
借用,即参考,但不允许改变参考目的地.该&运营商借用值所有权.
可变地借用,即参考改变目的地.该&mut运营商性情不定地借用一个值所有权.
首先,任何借入必须持续不超过所有者的范围.其次,您可能拥有这两种借款中的一种或另一种,但不能同时使用这两种:
- 一个或多个
&T资源的引用(),- 一个可变的引用(
&mut T).
我相信引用一个引用是创建一个指向值的指针并通过指针访问该值.如果存在更简单的等效实现,则编译器可以优化它.
但是,我不明白什么是移动的意思以及它是如何实现的.
对于实现Copy特征的类型,它意味着复制,例如通过从源分配结构成员,或者a memcpy().对于小结构或原始数据,此副本是有效的.
而对于招?
我一直认为将const locals设为const是件好事
void f() {
const resource_ptr p = get();
// ...
}
Run Code Online (Sandbox Code Playgroud)
然而,上周我观看了那些参与C++练习并且想知道返回const指针的学生
resource_ptr f() {
const resource_ptr p = get();
// ...
return p;
}
Run Code Online (Sandbox Code Playgroud)
在这里,如果编译器不能适用NRVO(想像一番情景下这是真的,也许返回两个指针之一,根据条件),突然const变成了pessimization因为编译器不能移动p,因为它是常量.
尝试避免const返回的当地人是一个好主意,还是有更好的方法来解决这个问题?
请考虑以下代码:
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A(int) { cout << "int" << endl; }
A(A&&) { cout << "move" << endl; }
A(const A&) { cout << "copy" << endl; }
};
int main()
{
vector<A> v
{
A(10), A(20), A(30)
};
_getch();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出是:
int
int
int
copy
copy
copy
Run Code Online (Sandbox Code Playgroud)
A(10),A(20)并且A(30)是临时对象,对不对?
那么为什么复制构造函数被调用?不应该调用移动构造函数吗?
路过move(A(10)),move(A(20)),move(A(30))相反,输出为:
int
move
int
move
int
move
copy …Run Code Online (Sandbox Code Playgroud) 我无法弄清楚为什么在最后一种情况下启用了复制省略时调用的移动构造函数(甚至是强制性的,例如在C++ 17中):
class X {
public:
X(int i) { std::clog << "converting\n"; }
X(const X &) { std::clog << "copy\n"; }
X(X &&) { std::clog << "move\n"; }
};
template <typename T>
X make_X(T&& arg) {
return X(std::forward<T>(arg));
}
int main() {
auto x1 = make_X(1); // 1x converting ctor invoked
auto x2 = X(X(1)); // 1x converting ctor invoked
auto x3 = make_X(X(1)); // 1x converting and 1x move ctor invoked
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,哪些规则阻碍了移动构造函数被省略?
UPDATE
调用移动构造函数时可能更直接的情况: …
c++ language-lawyer move-semantics copy-elision perfect-forwarding
所以这里有一个小测试程序:
#include <string>
#include <iostream>
#include <memory>
#include <vector>
class Test
{
public:
Test(const std::vector<int>& a_, const std::string& b_)
: a(std::move(a_)),
b(std::move(b_)),
vBufAddr(reinterpret_cast<long long>(a.data())),
sBufAddr(reinterpret_cast<long long>(b.data()))
{}
Test(Test&& mv)
: a(std::move(mv.a)),
b(std::move(mv.b)),
vBufAddr(reinterpret_cast<long long>(a.data())),
sBufAddr(reinterpret_cast<long long>(b.data()))
{}
bool operator==(const Test& cmp)
{
if (vBufAddr != cmp.vBufAddr) {
std::cout << "Vector buffers differ: " << std::endl
<< "Ours: " << std::hex << vBufAddr << std::endl
<< "Theirs: " << cmp.vBufAddr << std::endl;
return false;
}
if (sBufAddr != cmp.sBufAddr) {
std::cout …Run Code Online (Sandbox Code Playgroud) move-semantics ×10
c++ ×9
c++11 ×6
c++14 ×1
c++20 ×1
copy-elision ×1
inheritance ×1
move ×1
nrvo ×1
ownership ×1
performance ×1
rust ×1
stdstring ×1
stdvector ×1