Search code examples
c++constructorvirtualdestructorauto

Forced copy constructor in derived class with virtual destructor when using Auto


I was trying to derive std::unique_lock and I ran across an issue that I am not otherwise able to duplicate with a simpler class. Below is compilable code that replicates the problem:

#include <iostream>
#include <mutex>
#include <string>

template<class T>
class A : public std::unique_lock<T>
{
 public:
  template<typename... Args>
  A(const std::string name, Args&&... args) : name_(name), 
  std::unique_lock<T>(args...) { }
  virtual ~A() {}  // if this destructor exists...
  private:
  std::string name_;
};


int main()
{
  std::timed_mutex tm;
  auto a = A<std::timed_mutex>("Hello", tm, std::defer_lock); // this line fails
  A<std::timed_mutex> a("Hello", tm, std::defer_lock); // this line works
  return 0;
}

If the virtual destructor exists ( which I need for my actual class ), then I cannot use the line with auto to instantiate the class because ultimately the ctor in std::unique_lock gets called is the deleted one that takes a const T& mutex which is deleted (b/c the lock class can't deal with const mutexes). I assume its calling that deleted const ctor because for some reason its calling the copy constructor in A() that takes as input a const &A (according to the error logs below). If I simply use the non auto style instantiation code (labelled), then it compiles fine.

I'm using gcc 5.4.1 with the set(CMAKE_CXX_STANDARD 11) setting. I've tried 14 and 17 as well, so I assume its not the cpp I'm using.


Solution

  • The compiler will not implicitly generate a move-constructor if your class contains a user-declared destructor, copy-constructor, or assignment operator. Since A has no move-constructor, the compiler falls back to the copy-constructor, which is implicitly deleted because std::unique_lock's copy-constructor is deleted.

    You can explicitly declare a move-constructor to get things working:

    template<class T>
    class A : public std::unique_lock<T>
    {
    public:
      template<typename... Args>
      A(const std::string name, Args&&... args)
        : std::unique_lock<T>(args...),
          name_(name)
      { }
      A(A&&) = default;
      virtual ~A() {}
    private:
      std::string name_;
    };
    

    Live example

    You should also probably declare a move-assignment operator, but that's not necessary in this case.