Question

I've just been looking at solutions to another question (this one). It seems that it should be possible to create a custom deleter for a unique_ptr instance that can unlock a mutex.

For example,

#include <mutex>
#include <memory>

struct LockDeleter
{
  std::unique_lock<std::mutex> lock_;

  LockDeleter(std::mutex& m) : lock_(m) {}
  void operator()(void*) { lock_.unlock(); }
};

int main()
{
  std::mutex moo;

  {
    std::unique_ptr<int, LockDeleter> ptr(new int(42), LockDeleter(moo));
  }
}

Compiling this under VS2013 express, I get

Error 1 error C2280: 'std::unique_lock::unique_lock(const std::unique_lock &)' : attempting to reference a deleted function

and

This diagnostic occurred in the compiler generated function 'LockDeleter::LockDeleter(const LockDeleter &)'

Now, I can't seem to force the lock deleter instance to be moved, rather than copied using std::move... even adding an explicit move constructor to LockDeleter doesn't help, and the deleted copy constructor still gets called.

So, am I doing something silly, or must unique_ptr deleters always be copy constructable?

Was it helpful?

Solution

unique_ptr is required to support move-constructible deleters; 20.7.1.2.1 [unique.ptr.single.ctor]:

9 - [...] if [the deleter type] D is non-reference type A, then the [two-argument constructor] signatures are:

unique_ptr(pointer p, const A& d);
unique_ptr(pointer p, A&& d);

[...]

12 - Requires: [...]

  • [if d is a non-const rvalue then] D shall satisfy the requirements of MoveConstructible (Table 20), and the move constructor of D shall not throw an exception. This unique_ptr will hold a value move constructed from d. [...]

If we explicitly add a move constructor and delete the copy constructor from LockDeleter then we get a more informative error message; http://rextester.com/XFYUG91939:

Error(s):
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\memory(1243) : error C2280: 'LockDeleter::LockDeleter(const LockDeleter &)' : attempting to reference a deleted function
        source_file.cpp(10) : see declaration of 'LockDeleter::LockDeleter'
        C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\memory(1241) : while compiling class template member function 'std::_Unique_ptr_base<_Ty,_Dx,false>::_Unique_ptr_base(int *,_Dx)'
        with
        [
            _Ty=int
,            _Dx=LockDeleter
        ]
        C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\memory(1380) : see reference to function template instantiation 'std::_Unique_ptr_base<_Ty,_Dx,false>::_Unique_ptr_base(int *,_Dx)' being compiled
        with
        [
            _Ty=int
,            _Dx=LockDeleter
        ]
        C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\INCLUDE\memory(1331) : see reference to class template instantiation 'std::_Unique_ptr_base<_Ty,_Dx,false>' being compiled
        with
        [
            _Ty=int
,            _Dx=LockDeleter
        ]
        source_file.cpp(20) : see reference to class template instantiation 'std::unique_ptr<int,LockDeleter>' being compiled

Note the mention of std::_Unique_ptr_base<_Ty,_Dx,false>::_Unique_ptr_base(int *,_Dx); this indicates that the deleter parameter is incorrectly being copied into the internal base class, when it should be being moved.

The only workaround I can see is to make lock_ mutable, allowing a copy constructor to operate as a move constructor.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top