I have a framework which defines exception as a non-copyable class, from which we derived a copyable class (defining a copy constructor calling a non-copy base class constructor)
This works under g++, but not under MSVC 2013.
The following code will reproduce the problem:
#include <iostream>
using namespace std;
#if defined _MSC_VER
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif
class u {
u(const u&) = delete;
const u& operator=(const u&) = delete;/* the library we use defines it as const u& */
public:
u() { cout << __PRETTY_FUNCTION__ << "def" << endl; }
protected:
explicit u(int i) { cout << __PRETTY_FUNCTION__ << "int: " << i << endl; }
};
class e : public u {
public:
e() { cout << __PRETTY_FUNCTION__ << "def" << endl; }
e(const e& _e) : u(1) { cout << __PRETTY_FUNCTION__ << "cpy" << endl; }
e& operator=(const e& _e) { cout << __PRETTY_FUNCTION__ << endl; return *this; }
};
int foo() {
e _e;
throw _e;
return 0;
}
int main() {
try {
foo();
} catch(const e& _e) {
cout << "in catch e" << endl;
} catch(...) {
cout << "in catch..." << endl;
}
#if defined _MSC_VER
cout << "press enter to exit" << endl;
cin.get();
#endif
return 0;
}
MSVC complains about Error 1 error C2280: 'u::u(const u &)' : attempting to reference a deleted function
at the end of function foo().
g++ and clang both compile the code, and they don't use the copy constructor at all (the e object is moved), but neither will compile if e
isn't copy-constructable.
EDIT: I've edited the code to force a copy.
BTW, if the u
copy functions are not deleted (nor defined, pre-c++11 non-copyable), MSVC fails at link phase during u::u(const u&)
lookup. (unresolved external)
Is there a flaw in my code, or is this bug in MSVC?
class NonCopyable { public: NonCopyable (const NonCopyable &) = delete; NonCopyable & operator = (const NonCopyable &) = delete; protected: NonCopyable () = default; ~NonCopyable () = default; /// Protected non-virtual destructor }; class CantCopy : private NonCopyable {};
Copy constructor is not inherited.
It is necessary to pass object as reference and not by value because if you pass it by value its copy is constructed using the copy constructor. This means the copy constructor would call itself to make copy. This process will go on until the compiler runs out of memory.
Passing by value (rather than by reference) means a copy needs to be made. So passing by value into your copy constructor means you need to make a copy before the copy constructor is invoked, but to make a copy you first need to call the copy constructor.
Firstly, e
and u
both have user-declared copy-constructors. This means that they have no implicitly-generated move-constructor or move-assignment operator. (Therefore your claim "The e
object is moved" is false; more on this below).
e
is copyable, u
is not copyable. e
is considered movable because moving falls back to copying if there is no move-constructor.
According to section [except.throw] of C++14 (it was the same in C++11), the initialization of objects when you throw the exception is equivalent to:
e temp = e_; // (1)
const e& _e = temp; // (2)
For (1), no temporary is created, because e_
and temp
have the same type.
For (2) it is direct binding: _e
referes to temp
directly and there is no temporary again.
You may recall that (1) is a copy elision context. This means that there must exist an accessible copy or move constructor; but the compiler is allowed to skip the call to that constructor and use the same memory space for both objects.
This is probably what you meant by saying "The e
object is moved": you saw that it was not copied, but assumed a move when in fact it was copy elision.
Putting it all together: the only requirement for the throw
- catch
to work is that e
has a callable move-constructor or copy-constructor. (This constructor may not actually be called, but it must exist).
Your e
does in fact have a callable copy-constructor, so the code is correct and the MSVC version you tried was bugged.
If you still have access to that version, it would be interesting to try actually writing e temp = e_; const e& _e = temp;
and see if that code generates the same error message. That would show whether the bug is with the compiler's implementation of copy-initialization, as opposed to being a bug with its throwing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With