I'm adapting the idea of the deffered ptr Herb Sutter talked about in cppcon 2016, to be able to manage external resources represented by an id in a safer way.
Therefore I created a non copy able and only movable class holding the id
the resource should represent. Like a unique_ptr
the id
should become 0
if the object is moved to another object.
To my understanding you should still be allowed to use the object even after is was moved, if the called function does not have any preconditions, so to my understanding this should be valid:
int main() {
resource src = make_resource(10);
resource dst;
std::cout << "src " << src.get() << std::endl;
std::cout << "dst " << dst.get() << std::endl;
dst = std::move(src);
std::cout << "src " << src.get() << std::endl; // (*)
std::cout << "dst " << dst.get() << std::endl;
src = make_resource(40);
std::cout << "src " << src.get() << std::endl;
std::cout << "dst " << dst.get() << std::endl;
return 0;
}
But clang-tidy
give me this warning:
warning: 'src' used after it was moved [bugprone-use-after-move]
For the src.get()
after the dst = std::move(src)
(marked above).
So my questions are:
- Am I allowed to call
src.get()
after thestd::move(src)
- May I make the assumption that
src.get()
returns0
after thestd::move
. - If 1. and 2. are valid, then is there a way to change the code so that clan-tidy knows that this is valid. And if not is there a way to change the code that it is valid?
Here is the implementation of the class:
struct resource {
resource() = default;
// no two objects are allowed to have the same id (prevent double free, only 0 is allowed multiple times as it represents nullptr)
resource(const resource&) = delete;
resource& operator=(const resource& other) = delete;
// set the id of the object we move from back to 0 (prevent double free)
resource(resource&& other) noexcept : id(std::exchange(other.id, 0)) {}
resource& operator=(resource&& other) noexcept {
id = std::exchange(other.id, 0);
return *this;
}
// will free the external resource if id not 0
~resource() = default;
// returns the id representing the external resource
int get() const noexcept { return id; }
protected:
// only allow the make function to call the constructor with an id
friend resource make_resource(int id);
explicit resource(int id) : id(id) {}
protected:
int id = 0; // 0 = no resource referenced
};
// in the final version the id should be retrieved by from the external ip
resource make_resource(int id) { return std::move(resource(id)); }