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:
- Does this cause any problems? UB or otherwise? (seems fine to me?)
- 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:
- 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.
- 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.