4

While I was making myself familliar with C++14 specification, I have read that, if a class has no explicitly declared Copy Constructor, Copy Assignment Operator, Move Constructor nor Move Assignment Operator, default implementations should be generated by the compiler.

Consider this empty class for thread safe files:

class ThreadSafeFile
{
    std::mutex m_mutex;
    std::string m_FileName;
    std::ofstream m_File;
};

When I try to move assign it like this:

ThreadSafeFile file;

ThreadSafeFile file2 = std::move(file);

I am getting this compilation error:

function "ThreadSafeFile::ThreadSafeFile(const ThreadSafeFile &)" (declared implicitly) cannot be referenced -- it is a deleted function

Why is that so?

Alexander Demerdzhiev
  • 1,034
  • 2
  • 14
  • 29

5 Answers5

5

If you look carefully, std::mutex cannot be copied nor moved. Since you have a member that cannot be moved or copied, your move constructor and copy constructor are implicitly deleted. If you want to allow moving your class, you can always use std::unique_ptr.

struct ThreadSafeFile {
    std::unique_ptr<std::mutex> m_mutex;
    string m_FileName;
    std::ofstream m_File;
};

As T.C pointed out in the comments, implementing a move constructor that moves everything except the mutex can also be a valid solution for some cases. You can find a very good example here: How should I deal with mutexes in movable types in C++?

Community
  • 1
  • 1
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 2
    `std::ofstream` is movable, isn't it? http://en.cppreference.com/w/cpp/io/basic_ofstream/basic_ofstream http://en.cppreference.com/w/cpp/io/basic_ofstream/operator%3D – Fred Larson Nov 03 '16 at 14:05
  • Oh! That's true, I'm sorry, I'll correct that right away. – Guillaume Racicot Nov 03 '16 at 14:24
  • `unique_ptr` is probably not the correct solution for this use case. – T.C. Nov 03 '16 at 20:04
  • @T.C. unfortunately, I don't know other answer. What would you propose? – Guillaume Racicot Nov 03 '16 at 20:19
  • Depends on the intended behavior, but probably moving everything except the mutex is the way to go. See http://stackoverflow.com/questions/29986208/how-should-i-deal-with-mutexes-in-movable-types-in-c/29988626#29988626 – T.C. Nov 03 '16 at 20:49
2

Because std::mutex is not movable nor copyable (they're deleted) so your class' move constructor (and copy constructor) are implictly deleted as well.

Quoting cppreference:

std::mutex is neither copyable nor movable.

You get the complaint for the copy constructor because the move constructor has failed already (without giving you diagnostics about it) and as a fallback the copy constructor is invoked and fails as well, once that happens, you get an error.

ThreadSafeFile file2 = std::move(file);
//ThreadSafeFile::ThreadSafeFile(ThreadSafeFile&&) fails.

//try Copy con (implicitly):
ThreadSafeFile file2(file); 
//ThreadSafeFile::ThreadSafeFile(const ThreadSafeFile&) fails too.

//Error.

As for the reasoning why they're deleted, there's an answer for that already.

Community
  • 1
  • 1
Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
2

When I have such errors, I find it extremely helpful to compile a MCVE with different compilers. While some compiler might give you a criptic or even misleading error, another one might give you all you need.

In this case, clang has a very nice explanatory diagnostics:

error: call to implicitly-deleted copy constructor of 'ThreadSafeFile'
ThreadSafeFile DestinationOfMoving = std::move(file);
^                     ~~~~~~~~~~~~~~~

note: copy constructor of 'ThreadSafeFile' is implicitly deleted
because field 'm_mutex' has a deleted copy constructor
std::mutex m_mutex;
^

.../std_mutex.h:97:5: note: 'mutex' has been explicitly marked deleted here
mutex(const mutex&) = delete;
^

Note the very clear logical explanation:

note: copy constructor of 'ThreadSafeFile' is implicitly deleted
because field 'm_mutex' has a deleted copy constructor
std::mutex m_mutex;
^
bolov
  • 72,283
  • 15
  • 145
  • 224
1

In order for a class to have defaulted constructors such as copy, move etc, it is not just dependent on what constructors you explicitly declare, but also what the constructors of its members are.

In this case, std::mutex and std::ofstream have their copy constructors deleted, and thus, so must your class.

However, this does not prevent you from creating your own, explicit move constructor that handles the fact that it's deleted in its members - the compiler can't generate a default one for you though.

Olipro
  • 3,489
  • 19
  • 25
  • [ofstream does have a move constructor](http://en.cppreference.com/w/cpp/io/basic_ofstream/basic_ofstream) – BeyelerStudios Nov 03 '16 at 14:01
  • Yes, it does, but we're talking about copy construction. – Olipro Nov 03 '16 at 14:01
  • no *we* are not *the compiler* is, but that's another problem – BeyelerStudios Nov 03 '16 at 14:02
  • @BeyelerStudios What are you talking about? There is no issue in C++ with performing copy construction or move construction - I suggest you read the reference documentation and gain an understanding of lvalues vs (the various kinds of) rvalues. – Olipro Nov 03 '16 at 14:03
  • `ThreadSafeFile file2 = std::move(file);` is **not** talking about copy construction – BeyelerStudios Nov 03 '16 at 14:04
  • That is incongruous with the compiler's error statement because that would be invoking the move assignment operator which is obviously `ThreadSafeFile::operator=(ThreadSafeFile&&)` – Olipro Nov 03 '16 at 14:05
  • @olipro Because it falls back to copy assignment when moving can't be done. Then, it finds out copying can't be done either, and it errors. – Hatted Rooster Nov 03 '16 at 14:06
  • that's a problem with compiling C++, it first and foremost should invoke the move constructor, when that fails though it may invoke the copy constructor, sad part about reporting C++ errors: you usually get the last failure – BeyelerStudios Nov 03 '16 at 14:07
  • @GillBates Still not adding up, that would be `ThreadSafeFile::operator=(const ThreadSafeFile&)` – Olipro Nov 03 '16 at 14:07
  • `A a = b;` is equivalent to `A a(b);` and usually results in a call to a constructor, not an assignment (assignment would require a fully constructed object, but you don't expect a default constructor call for `a` do you?) – BeyelerStudios Nov 03 '16 at 14:08
  • Ah yes of course, it's in a declaration so that explains the error. I still wouldn't call it a problem of C++ - the compiler could certainly provide you with a better explanation of what's going on, it just doesn't, I wouldn't blame the language for it. – Olipro Nov 03 '16 at 14:10
  • the point was: we're talking about why the move constructor cannot implicitly be generated, the aside is that the compiler diagnostics suck (leaving out the question of blame) – BeyelerStudios Nov 03 '16 at 14:12
1

gcc says:

note: 'ThreadSafeFile::ThreadSafeFile(ThreadSafeFile&&)' is implicitly deleted because the default definition would be ill-formed:
 class ThreadSafeFile{
       ^
error: use of deleted function 'std::mutex::mutex(const std::mutex&)'

http://en.cppreference.com/w/cpp/language/move_constructor

user5821508
  • 332
  • 2
  • 10