手写移动
Hand written move
我写了一个矢量 class 来学习移动语义。
我使用移动构造函数移动 T(注释行)。
我的问题是为什么不像 C 语言那样只复制临时对象的所有字节并将临时对象的所有字节设置为零?
我知道将为临时对象调用析构函数,它可能需要一些已初始化的成员才能正确析构。这可能是我不能更改对象的内部表示的原因。
但如果我 100% 确定我的 ~T() 没有这样的要求,这是一个很好的优化吗?
20 void push_back(T&& val)
21 {
22 check_cap();
23 //new (m_data + m_size) T(std::move(val));
24 for(int i = 0; i < sizeof(T); ++i)
25 {
26 reinterpret_cast<char*> (m_data + m_size)[i] = reinterpret_cast<char*> (&val)[i];
27 reinterpret_cast<char*> (&val)[i] = 0;
28 }
29 m_size++;
30 }
(如果与实际问题无关,请不要透露任何有关演员表及其安全性的信息)
(我知道这不是一个好方法,最好不要在实际项目中使用它。但我只关心从效率的角度来看它有多好。)
why not to just copy all the bytes of temp object and set all bytes of temp object to zero like in C?
is this a good optimization?
我认为不是。这是您的手写版本与使用 clang++
的普通版本之间的 quick-bench 比较:
当使用 g++
时,结果是 a tie,所以那里也没有增益。
您的优化在少数情况下可能有效。当您的移动构造函数执行特定操作时,它可能会导致问题。
class MyCustomClass : public IObserver
{
Registry ®istry;
// ...
public:
MyCustomClass(MyCustomClass &&rhs)
: registry{rhs.registry}
{
registry.register(*this);
}
// ...
};
如果您按位复制此 class 移动的实例未在注册表中注册。
通过清零,引用会被破坏,原始实例的析构函数很可能会在从注册表中注销时崩溃。
你的计划很糟糕。
编译器可以使用 the as-if rule,经常计算出 memcpy 和零 - 或者甚至只是跳过析构函数 - 是合法的。
如果这样的编译器遇到您手工制作的未定义行为,它要么被未定义行为混淆,要么因此无法进一步优化。
有时有充分的理由诉诸未定义的行为。但他们首先要证明您的解决方案能让事情变得更好,然后用尽所有符合标准的解决方案。他们以对真正的短期、中期和长期风险的坦诚讨论结束,换取的只是短期有保障的收益。
您的情况 none 如此。
优化 move-destroy 到 memcpy 是编译器已经对在简单代码流中易于理解的类型所做的事情。手动操作 99/100 毫无意义,90/100 倍有害。
如果您的类型不简单且代码流程不易于理解,那么您的优化可能也很难证明是安全的。而且,如果您简化了类型和代码流,到时候您可以可靠地证明您的 memcpy 零是最佳的,您的编译器可能也可以。
坐下来使用 Godbolt 并使用优化的编译器输出。很有教育意义。
我写了一个矢量 class 来学习移动语义。 我使用移动构造函数移动 T(注释行)。
我的问题是为什么不像 C 语言那样只复制临时对象的所有字节并将临时对象的所有字节设置为零?
我知道将为临时对象调用析构函数,它可能需要一些已初始化的成员才能正确析构。这可能是我不能更改对象的内部表示的原因。
但如果我 100% 确定我的 ~T() 没有这样的要求,这是一个很好的优化吗?
20 void push_back(T&& val)
21 {
22 check_cap();
23 //new (m_data + m_size) T(std::move(val));
24 for(int i = 0; i < sizeof(T); ++i)
25 {
26 reinterpret_cast<char*> (m_data + m_size)[i] = reinterpret_cast<char*> (&val)[i];
27 reinterpret_cast<char*> (&val)[i] = 0;
28 }
29 m_size++;
30 }
(如果与实际问题无关,请不要透露任何有关演员表及其安全性的信息)
(我知道这不是一个好方法,最好不要在实际项目中使用它。但我只关心从效率的角度来看它有多好。)
why not to just copy all the bytes of temp object and set all bytes of temp object to zero like in C?
is this a good optimization?
我认为不是。这是您的手写版本与使用 clang++
的普通版本之间的 quick-bench 比较:
当使用 g++
时,结果是 a tie,所以那里也没有增益。
您的优化在少数情况下可能有效。当您的移动构造函数执行特定操作时,它可能会导致问题。
class MyCustomClass : public IObserver
{
Registry ®istry;
// ...
public:
MyCustomClass(MyCustomClass &&rhs)
: registry{rhs.registry}
{
registry.register(*this);
}
// ...
};
如果您按位复制此 class 移动的实例未在注册表中注册。
通过清零,引用会被破坏,原始实例的析构函数很可能会在从注册表中注销时崩溃。
你的计划很糟糕。
编译器可以使用 the as-if rule,经常计算出 memcpy 和零 - 或者甚至只是跳过析构函数 - 是合法的。
如果这样的编译器遇到您手工制作的未定义行为,它要么被未定义行为混淆,要么因此无法进一步优化。
有时有充分的理由诉诸未定义的行为。但他们首先要证明您的解决方案能让事情变得更好,然后用尽所有符合标准的解决方案。他们以对真正的短期、中期和长期风险的坦诚讨论结束,换取的只是短期有保障的收益。
您的情况 none 如此。
优化 move-destroy 到 memcpy 是编译器已经对在简单代码流中易于理解的类型所做的事情。手动操作 99/100 毫无意义,90/100 倍有害。
如果您的类型不简单且代码流程不易于理解,那么您的优化可能也很难证明是安全的。而且,如果您简化了类型和代码流,到时候您可以可靠地证明您的 memcpy 零是最佳的,您的编译器可能也可以。
坐下来使用 Godbolt 并使用优化的编译器输出。很有教育意义。