Why std::sort construct objects?

Viewed 400

I created the following class to understand the behavior of std::sort:

class X {
public:
  X(int i) : i_(i) { }
  X(X&& rhs) noexcept : i_(std::move(rhs.i_)) { mc_++; }
  X& operator=(X&& rhs) noexcept {
    i_ = std::move(rhs.i_); ao_++; return *this;
  }
  void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); sw_++; } 
  friend bool operator<(const X& lhs, const X& rhs) {
    return lhs.i_ < rhs.i_;
  }
  static void reset() { mc_ = ao_ = sw_ = 0; }
private:
  int i_;
  static size_t mc_, ao_, sw_; // function-call counters
};
// void swap(X& lhs, X& rhs) { lhs.swap(rhs); }

And run the following benchmark code:

int main() {
  std::vector<X> v;
  for (int i = 0; i < 1000000; i++) v.emplace_back(i);

  std::mt19937 g(0xa41bc9); // fixed seed to compare measurements
  std::shuffle(v.begin(), v.end(), g);

  X::reset();
  std::sort(std::begin(v), std::end(v));
}

The whole code in online IDE is here: https://wandbox.org/permlink/nbwRKptakgCSHK4f.

The measured number of calls of particular functions are as follows (all with -O2 or /O2 flags):

   function:   move ctor    operator=        swap
  GCC 7.1.0:   5,007,335   11,700,048           0
clang 4.0.0:   4,932,061    9,973,899           0
 MSVC 19.11:   8,580,356   21,521,211           0

If I uncomment the swap function, situation gets better:

   function:   move ctor    operator=        swap
  GCC 7.1.0:     999,999    3,685,376   4,007,336
clang 4.0.0:      72,554      254,885   4,859,507
 MSVC 19.11:     906,593    6,173,685   7,673,763

However, there still remains a lot of calls of the move constructor (plus destructor) and the move assignment operator. What bothers me is the efficiency. For instance, calls of swap and operator= can be inlined by the compiler, but I guess the compiler may not "inline" (optimize out) the construction/destruction of objects.

Why is construction/assignment of objects used in sorting? In-place sorting (which usually std::sort does) can be implemented purely by compare and swap operations.

UPDATE

My assumption was wrong. It seems perfectly legal to optimize out object creation/destruction, such as in:

X temp = std::move(x1);  
x1 = std::move(x2);
x2 = std::move(temp);

Therefore, such code can be as efficient as custom swap. Online example: https://godbolt.org/g/ud4u9U - there are no calls of move constructor / assignment operator, though these are not trivial, and their functionality is inlined into main.

1 Answers
Related