I have a wrapper to legacy code. In this legacy code the function that duplicates an object is not thread safe (when calling on the same first argument), therefore it is not marked const
. I guess following modern rules: https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
class A{
L* impl_; // the legacy object has to be in the heap
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
This duplicate
looks like a good way to implement a copy constructor, except for the detail that is it not const
. Therefore I cannot do this:
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
};
So what is the way out this paradoxical situation?
(Let's say also that legacy_duplicate
is not thread-safe but I know leaves the object in the original state when it exits. Being a C-function the behavior is only document but has no concept of constness.)
I can think of many possible scenarios:
(1) One possibility is that there is no way to implement a copy constructor with the usual semantics at all. (Yes, I can move the object and that is not what I need.)
(2) On the other hand copying an object is inherently non-thread-safe in the sense that copying a simple type can find the source in an half-modified state, so I can just go forward and do this perhaps,
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
};
(3) or even just declare duplicate
const and lie about thread safety in all contexts. (After all the legacy function doesn't care about const
so the compiler will not even complain.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &L); return ret;}
};
(4) Finally, I can follow the logic and make a copy-constructor that takes a non-const argument.
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
};
It turns out that this works in many contexts, because these objects are not usually const
.
The question is, it this a valid or common route?
I cannot name them, but I intuitively expect lots of problems down the road of having a non-const copy constructor.
(5) Finally, although this seems to be an overkill and could have a steep runtime cost, I could add a mutex:
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &L); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(other.mut);
L* ret; legacy_duplicate(impl_, &L); return ret;
}
mutable std::mutex mut;
};
But being forced to do this looks like pessimization and makes the class bigger. I am not sure. I am currently leaning towards (4), or (5) or a combination of both.
Aucun commentaire:
Enregistrer un commentaire