Home > Blockchain >  move assignment from newly constructed object to *this in a member function
move assignment from newly constructed object to *this in a member function

Time:02-14

Implementing a reset() method, which uses some of the members from this to construct a new object and then move-assign that object to *this.

Questions:

  1. Does this cause any problems? UB or otherwise? (seems fine to me?)
  2. Is there a more idiomatic way?

Destructor and move-assignment operator only implemented to prove what is happening/ The real class has neither. "Rule of 5" not followed, as not needed here.

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <numeric>
#include <ostream>
#include <random>
#include <vector>

struct Thing { // NOLINT Rule of 5 not followed, because debug only
public:
  std::size_t      size;
  std::vector<int> v;

  explicit Thing(std::size_t size_) : size(size_), v(size_) {
    std::cerr << "Thing constructor\n";
    std::iota(v.begin(), v.end(), 1);
  }

  ~Thing() { std::cerr << "Thing destructor\n"; } // purely for debugging

  Thing& operator=(Thing&& other) noexcept {  // purely for debugging
    std::cerr << "Thing Move Assignment operator\n";
    size = other.size;
    v    = std::move(other.v);
    return *this;
  }

  void shuffle() {
    std::shuffle(v.begin(), v.end(), std::mt19937{1}); // NOLINT fixed seed
  }
  void reset() {
    // move assignment operator from a newly constructed object. Constructor uses SOME of the
    // member variables of *this
    *this = Thing(size);
  }

  friend std::ostream& operator<<(std::ostream& os, const Thing& t) {
    os << "[";
    const char* delim = "";
    for (const auto& e: t.v) {
      os << delim << e;
      delim = ",";
    }
    return os << "]";
  }
};

int main() {
  std::cerr << "Before inital construction\n";
  auto t = Thing(10);
  std::cout << t << "\n";
  t.shuffle(); // somehow modify the object
  std::cerr << "Before reset\n";
  std::cout << t << "\n";
  t.reset(); // reset using
  std::cerr << "after reset\n";
  std::cout << t << "\n";
}

Output:

Before inital construction
Thing constructor
[1,2,3,4,5,6,7,8,9,10]
Before reset
[10,1,3,6,8,5,7,4,2,9]
Thing constructor
Thing Move Assignment operator
Thing destructor
after reset
[1,2,3,4,5,6,7,8,9,10]
Thing destructor

CodePudding user response:

  1. Does this cause any problems? UB or otherwise? (seems fine to me?)

As long as the move assignment and the constructor are implemented in such a way that it does what you want, then I don't see a problem.

  1. Is there a more idiomatic way?

I would invert the direction of re-use.

explicit Thing(std::size_t size_) : size(size_), v(size_) {
    reset();
}

void reset() {
    v.resize(size_);
    std::iota(v.begin(), v.end(), 1);
}

If size is supposed to always match the size of the vector, then I would recommend removing the member since the size is already stored inside the vector. This has a caveat that in such case reset won't be able to restore a moved-from thing to its earlier size. But that's typically reasonable limitation.

  •  Tags:  
  • c
  • Related