2

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?

Community
  • 1
  • 1
Rook
  • 5,734
  • 3
  • 34
  • 43
  • Seems to be a compiler bug; gcc and clang are OK with it. – ecatmur May 12 '14 at 15:35
  • 1
    @ecatmur: interesting. During my experimentation, I noticed that vs2013 doesn't support `std::bind` with moved parameter values either (see examples [here](http://stackoverflow.com/questions/8640393/move-capture-in-lambda)) so I guess VS2013 has multiple subtle issues with non-copyable objects. – Rook May 12 '14 at 15:39
  • @ecatmur: indeed, firing up some of my crufty old debian VMs shows that GCC 4.6 and 4.8 both seem quite happy to compile this code. Feel free to post that little comment as an answer. – Rook May 12 '14 at 16:18
  • 1
    VS2013 RTM does not generate implicit move members (the CTP release does). You need to implement a move constructor yourself. EDIT: [does not seem to help.](http://rextester.com/XFYUG91939) Must be a standard library bug? – Casey May 12 '14 at 16:38
  • @Casey adding an explicit move constructor, `LockDeleter(LockDeleter&& rhs) : lock_(std::move(rhs.lock_)) {}`, and adding `std::move(LockDeleter(mtx_)` did not help under VS2013, and did not break anything under GCC, so I'm assuming my code is okay and that VS2013 (at least the version of it I am using, 12.0.21005.1 REL) is at fault. – Rook May 12 '14 at 16:46
  • 3
    Digging through VS12's ``: `unique_ptr`'s constructor passes the deleter parameter to a base class by value, hence (incorrectly) instantiating the deleter's copy constructor. – Casey May 12 '14 at 17:04
  • @Casey supported by the error message in http://rextester.com/XFYUG91939 : `std::_Unique_ptr_base<_Ty,_Dx,false>::_Unique_ptr_base(int *,_Dx)` so it's being passed by value. – ecatmur May 12 '14 at 17:05

1 Answers1

1

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.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366