8

Here's an issue I often run into with RAII. I was wondering if anyone had a good solution for it.

Start with your standard RAII utility class:

class RAIIHelper {
  RAIIHelper() {
    AcquireAResource();
  }
  ~RAIIHelper() {
    ReleaseTheResource();
  }
};

Now, for various reasons, I need to make it a template. Let's also say its constructor takes an argument of the template parameter type:

template <typename T>
class RAIIHelper {
  RAIIHelper(T arg) {
    AcquireAResource();
  }
  ~RAIIHelper() {
    ReleaseTheResource();
  }
};

Now consider a use site:

void func() {
  RAIIHelper<SomeType> helper(someObj);
}

It's annoying to have to write out SomeType when it can be deduced from someObj, so I write a helper function to deduce the type:

template <typename T>
RAIIHelper<T> makeRAIIHelper(T arg) {
  return RAIIHelper<T>(arg);
}

Now I can use it like so:

void func() {
  auto helper = makeRAIIHelper(someObj);
}

Wonderful, right? Except there's a snag: RAIIHelper is now required to be copyable or movable, and the destructor - which releases the resource - can potentially be called twice: once for the temporary returned by makeRAIIHelper, and once for the local variable in the calling function.

In practice, my compiler performs RVO and the destructor is called only once. However, this is not guaranteed. This can be seen from the fact that if I try to give RAIIHelper a = delete'd move constructor, the code no longer compiles.

I could add additional state to RAIIHelper so that it knows not to call ReleaseTheResource() after it's been moved-from, but that's extra work that was unnecessary before I added makeRAIIHelper() to get the type deduction.

Is there a way I can get the type deduction, without having to add extra state to RAIIHelper?

HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • Beside it looking like you are reinventing `unique_ptr` there, are you sure there's absolutely no natural sentinel object of type `SomeType`? – Deduplicator Jul 14 '14 at 18:36
  • You can use a `unique_ptr`. – Homero C. de Almeida Jul 14 '14 at 18:37
  • Write a proper move constructor and disable the copy constructor. This shouldn't be a problem. – Kerrek SB Jul 14 '14 at 18:37
  • @KerrekSB Why disable the copy ctor? Edit: not sure if one can conclude from the OP that it's broken – dyp Jul 14 '14 at 18:38
  • 1
    The fact that everyone is still writing `std::lock_guard lock(m_mutex);` is evidence that you can't do this without a type that's either movable or copyable. – T.C. Jul 14 '14 at 18:39
  • 2
    @T.C.: I think there's more to it than that. You could have a deducing function for `unique_lock`, say. But it's dangerous: `{ auto x = make_lock(mx_); /* ... */ }` is OK, but it's easy to write the erroneous `{ make_lock(mx_); /* ... */ }` instead. – Kerrek SB Jul 14 '14 at 18:48
  • @KerrekSB: There's a reason GCC has `__attribute__((warn_unused_result))` and MS has `_Check_return_`. – Deduplicator Jul 14 '14 at 19:51

2 Answers2

8

There is a very simple solution: Use a reference to the temporary object instead of copying it into a local variable.

void func()
{
    auto&& helper = makeRAIIHelper(someObj);
}
nosid
  • 48,932
  • 13
  • 112
  • 139
  • 5
    And if you replace `return RAIIHelper(arg);` with "direct-initialization" à la `return {arg}`, the type doesn't need to be movable. – dyp Jul 14 '14 at 18:44
  • How will that work on the next line when the temporary object goes out of scope? – Stian Svedenborg Jul 14 '14 at 18:45
  • 4
    @StianV.Svedenborg The lifetime of that temporary is extended until the end of the scope. – dyp Jul 14 '14 at 18:45
  • @dyp: Thanks, that's important to mention. – nosid Jul 14 '14 at 18:47
  • 1
    @dyp: Are you sure it's *guaranteed* that no temporary object is created? – Kerrek SB Jul 14 '14 at 18:52
  • @KerrekSB Is is copy-list-initialization. See http://stackoverflow.com/q/7935639 and [stmt.return]/2 – dyp Jul 14 '14 at 18:55
  • 1
    @dyp: Hm, doesn't "copy"-list-initialization sound like it would be allowed to have a copy? Like `X x = { a, b, c };`: that one requires `X x = X { a, b, c };` to be well-formed, non? – Kerrek SB Jul 14 '14 at 19:04
  • 2
    @KerrekSB IIRC copy-list-initialization is the same as direct-list-initialization, except that the former will not work with an explicit constructor. – Praetorian Jul 14 '14 at 19:17
  • @dyp: Bummer that my code needs to compile with gcc 4.4, which doesn't support `return {arg}` yet. Otherwise this solution is perfect! – HighCommander4 Jul 14 '14 at 19:21
1

Building on the previous answers and comments:

You could leave the responsibility of being movable to unique_ptr, and return your resource like this:

template <class T>
auto makeRAII( T arg ) -> std::unique_ptr<RAIIHelper> {
     return make_unique(RAIIHelper<T>(arg));
}

Now it scopes like a static variable, but may be uncopyable & unmovable.

Stian Svedenborg
  • 1,797
  • 11
  • 27