52

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
  • 353,942
  • 115
  • 666
  • 851
amazingjxq
  • 4,487
  • 7
  • 33
  • 35

2 Answers2

129

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.1, 2 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.


1 http://eel.is/c++draft/class.mem#class.copy.ctor-6.sentence-3

2 http://eel.is/c++draft/class.mem#class.copy.assign-2.sentence-3

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • "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
  • I really disagree that implicit copy constructor are bad. I think you meant, implicit move contructors are bad. – user13947194 Jul 07 '23 at 06:35
  • @user13947194 The only situations I can think of where implicit move is incorrect, implicit copy is also incorrect, but anytime you want unique ownership, implicit copying has to be removed – Caleth Jul 07 '23 at 11:20
  • "bad" only in the presence of a user-declared destructor. References added to the answer. [Demo](https://gcc.godbolt.org/z/e5TscEf1Y). – Howard Hinnant Jul 07 '23 at 11:36
  • @Caleth you guys are right. Implicit copy can do what is not intended. But looking at it more you still thinking narrow as implicit copy is what we want tonnes of times. Imagine creating a simple structure and have to explicitly make a copy constructor for it. Dynamic allocation of memory be it direct or indirect is the corner case and you should be a man and stand your responsibility. My point stands that implicit move constructor are much worse than implicit copy as implicit move is just another implicit copy. It doesnt stop the original object from still pointing to the resource. – user13947194 Jul 07 '23 at 15:27
  • Which can cause the resource to be deleted twice in the upcoming destructors. By the time the SigSev exception is raised, it can be very very very hard to track down the cause. And you might even have to explicitly create every version of the constructors just to put print messages in them. Alright yes, same problem with implicit copy constructors. But I would argue, you expect an implicit move constructor to actually move resources; not just copy memory. – user13947194 Jul 07 '23 at 15:32
  • Where the copy members are merely deprecated, the move members are already deleted. No one is claiming that implicit move members are safer than implicit copy members. However if you believe the standard specification is in error, or could be improved, here is the procedure for proposing a change: https://isocpp.org/std/submit-a-proposal – Howard Hinnant Jul 07 '23 at 20:57
33

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
  • 62,044
  • 9
  • 127
  • 211
  • *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