In C++14, I am trying to define a "RAII factory" function, something like this:
// Here, "ScopedResource" is a plain RAII class, managing some resource.
ScopedResource factory(/* args */) {
return ScopedResource(/* args */);
}
Client usage would be:
{
auto scoped = factory(/* args */));
// use scoped...
}
As I understand it, copy elision is not guaranteed by the language in C++14, so this is not reliable.
In particular, it could result in ScopedResource's destructor being called at the end of the factory()
function (and a new copy made at the callsite). I don't want that.
The traditional client code works fine, of course:
{
ScopedResource scoped(/* args */);
// use scoped...
}
Now, I have tried removing ScopedResource
's copy constructor (=delete
), and defining a move constructor. The code compiles, and it only does construct/destruct once, which is what I want — but is it portable?
So, questions:
Or are there some other nuances here that I am missing?
Am I right that returnining an RAII object (where the destructor does special work) is not feasible before C++17?
In general, no. An RAII type should be built to handle being copied/moved so that it can be returned by value.
In your specific case you are correct that you need C++17's guaranteed copy elision since your RAII type does not do that. (Which means it's not really an RAII type)
Is my approach of removing the copy constructor and defining a move constructor valid/portable?
It's a valid way to stop an object from being copied, but again before C++17 the object could be moved out of the function and the destructor ran on the stub that is left in the function. If you goal is the destructor is not called until the object is destroyed in the call site then this is not the solution for you.
Am I right that in C++17, you can portably do it with the naïve code above?
Yes. In C++17 and beyond
ScopedResource factory(/* args */) {
return ScopedReseource(/* args */);
}
auto scoped = factory(/* args */));
boils down to
auto scoped = ScopedReseource(/* args */);
If you can't guarantee that you'll have C++17, then you can use a std::unique_ptr
to encapsulate your object. This will ensure that even if RVO/NRVO is not applied you object is not destroyed. That would give you
std::unique_ptr<ScopedResource> factory(/* args */) {
return make_unique<ScopedReseource>(/* args */);
}