How to write move so that it can potentially be op

2019-08-01 22:31发布

Given the following code:

struct obj {
    int i;
    obj() : i(1) {}
    obj(obj &&other) : i(other.i) {}
};

void f() {
    obj o2(obj(obj(obj{})));
}

I expect release builds to only really create one object and never call a move constructor because the result is the same as if my code was executed. Most code is not that simple though, I can think of a few hard to predict side effects that could stop the optimizer from proving the "as if":

  1. changes to global or "outside" things in either the move constructor or destructor.
  2. potential exceptions in the move constructor or destructor (probably bad design anyway)
  3. internal counting or caching mechanisms changing.

Since I don't use any of these often can I expect most of my moves in and out of functions which are later inlined to be optimized away or am I forgetting something?

P.S. I know that just because an optimization is possible does not mean it will be made by any given compiler.

2条回答
倾城 Initia
2楼-- · 2019-08-01 23:01

Using std::move( tmp(...) ) is completely pointless, the temporary tmp is already an rvalue, you don't need to use std::move to cast it to an rvalue.

Read this series of articles: Want Speed? Pass By Value

You'll learn more and understand better than you will by asking a question on Stackoverflow

查看更多
Luminary・发光体
3楼-- · 2019-08-01 23:07

This doesn't really have anything to do with the as-if rule. The compiler is allowed to elide moves and copies even if they have some side effect. It is the single optimization that a compiler is allowed to do that might change the result of your program. From §12.8/31:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.

So the compiler doesn't have to bother inspecting what happens inside your move constructor, it will likely get rid of any moves here anyway. To demonstrate this, consider the following example:

#include <iostream>

struct bad_mover
{
  static int move_count;
  bad_mover() = default;
  bad_mover(bad_mover&& other) { move_count++; }
};

int bad_mover::move_count = 0;

int main(int argc, const char* argv[])
{
  bad_mover b{bad_mover(bad_mover(bad_mover()))};
  std::cout << "Move count: " << bad_mover::move_count << std::endl;
  return 0;
}
  1. Compiled with g++ -std=c++0x:

    Move count: 0
    
  2. Compiled with g++ -std=c++0x -fno-elide-constructors:

    Move count: 3
    

However, I would question any reason you have for providing a move constructor that has additional side effects. The idea in allowing this optimization regardless of side effects is that a copy or move constructor shouldn't do anything other than copy or move. The program with the copy or move should be exactly the same as without.

Nonetheless, your calls to std::move are unnecessary. std::move is used to change an lvalue expression to an rvalue expression, but an expression that creates a temporary object is already an rvalue expression.

查看更多
登录 后发表回答