Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Throwing copyable class deriving from noncopyable

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?

like image 427
Bastien Durel Avatar asked Apr 10 '15 14:04

Bastien Durel


People also ask

How do I make my class non copyable?

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 {};

Can copy constructor be inherited?

Copy constructor is not inherited.

Why is it necessary to pass an object by reference in a copy constructor?

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.

Why can not we pass an object by value to a copy constructor?

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.


1 Answers

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.

like image 95
M.M Avatar answered Sep 20 '22 10:09

M.M