46

While I'm reading boost/shared_ptr.hpp, i saw this code:

//  generated copy constructor, destructor are fine...

#if defined( BOOST_HAS_RVALUE_REFS )

// ... except in C++0x, move disables the implicit copy

shared_ptr( shared_ptr const & r ): px( r.px ), pn( r.pn ) // never throws
{
}

#endif

What does the comment "generated copy constructor, destructor are fine except in C++11, move disables the implicit copy" mean here? Shall we always write the copy ctor ourselves to prevent this situation in C++11?

Nawaz
  • 341,464
  • 111
  • 648
  • 831
amazingjxq
  • 4,207
  • 6
  • 31
  • 35

2 Answers2

119

I've upvoted ildjarn's answer because I found it both accurate and humorous. :-)

I'm providing an alternate answer because I'm assuming because of the title of the question that the OP might want to know why the standard says so.

background

C++ has implicitly generated copy members because if it didn't, it would've been still-born in 1985 because it was so incompatible with C. And in that case we wouldn't be having this conversation today because C++ wouldn't exist.

That being said, implicitly generated copy members are akin to a "deal with the devil". C++ couldn't have been born without them. But they are evil in that they silently generate incorrect code in a significant number of instances. The C++ committee isn't stupid, they know this.

C++11

Now that C++ has been born, and has evolved into a successful grownup, the committee would just love to say: we're not doing implicitly generated copy members any more. They are too dangerous. If you want an implicitly generated copy member you have to opt-in to that decision (as opposed to opt-out of it). However considering the amount of existing C++ code that would break if this was done, that would be tantamount to suicide. There is a huge backwards compatibility concern that is quite justified.

So the committee reached a compromise position: If you declare move members (which legacy C++ code can't do), then we're going to assume that the default copy members are likely to do the wrong thing. Opt-in (with =default) if you want them. Or write them yourself. Otherwise they are implicitly deleted. Our experience to-date in a world with move-only types indicates that this default position is actually quite commonly what is desired (e.g. unique_ptr, ofstream, future, etc.). And the expense of opting-in is actually quite small with = default.

Looking Forward

The committee would love to even say: If you've written a destructor, it is likely that the implicit copy members are incorrect, so we will delete them. This is the C++98/03 "rule of three". However even that would break lots of code. However the committee has said in C++11 that if you provide a user-declared destructor, the implicit generation of copy members is deprecated. That means that this feature could be removed in a future standard. And that any day now your compiler might start issuing "deprecated warnings" in this situation (the standard can not specify warnings).

Conclusion

So be forewarned: C++ has grown up and matured over the decades. And that means that your father's C++ may need migrating to deal with your child's C++. It is a slow, gradual process so that you don't throw up your hands and just port to another language. But it is change, even if slow.

Howard Hinnant
  • 192,948
  • 49
  • 425
  • 554
  • "any day now your compiler might start issue "deprecated warnings" in this situation" sounds like another good addition for -Wdeprecated. And a warning on uses of _dynamic-exception-specification_s as well. – bames53 Jun 29 '12 at 14:29
  • 2
    +1. Awesome explanation. Quite sensible decision taken by the committe. – Nawaz Jun 29 '12 at 17:39
  • 1
    Great explanation on the *why* part of the question. – Aaron S Sep 04 '17 at 00:36
31

Because the C++ standard says so – §12.8/7:

If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted. The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. Thus, for the class definition

struct X {
    X(const X&, int);
};

a copy constructor is implicitly-declared. If the user-declared constructor is later defined as

X::X(const X& x, int i =0) { /* ... */ }

then any use of X’s copy constructor is ill-formed because of the ambiguity; no diagnostic is required.

(Emphasis mine.)

ildjarn
  • 60,915
  • 9
  • 122
  • 205
  • *no diagnostic is required.*? What does it mean? No error/warning will be issued for the ambiguity? – Nawaz Jun 29 '12 at 16:17
  • 1
    @Nawaz : One _can_ be issued, but is not _required_ to be issued, since it's only deprecated at this point. – ildjarn Jun 29 '12 at 16:23