Nayana Adassuriya
Nayana Adassuriya

Reputation: 24766

Is a copy constructor always created by default?

As I knew until today there are four default things create when creating a new class. "Default constructor", "Destructor", "Copy constructor" and "Assignment operator". But today when I was going trough a C++ article, it said that there can be situations where the copy constructor is not create by default.

  1. Is that true?
  2. If it is, in which situations?
  3. In those situations, how can an instance of that class be passed by value?

Upvotes: 6

Views: 3046

Answers (2)

juanchopanza
juanchopanza

Reputation: 227370

1) Yes, there can be situations where the copy constructor is not created by default.

2) The conditions where the implicitly declared default constructor is deleted are laid out in 12.8 Copying and moving class objects [class.copy]:

12.8.7 is about how the declaration of other special member functions affect the implicitly declared copy constructor. Whenever the class declares a copy constructor, a move constructor or a move assignment operator. If it declares any one of those, then you don't get an implicitly declared one.

...

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 (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.

12.8.11 is about how the data members and base classes affect the implicitly declared copy constructor. Essentially, if the class has any data members or base classes that are not copyable, the implicitly declared copy constructor is deleted:

11 An implicitly-declared copy/move constructor is an inline public member of its class. A defaulted copy/ move constructor for a class X is defined as deleted (8.4.3) if X has:

— a variant member with a non-trivial corresponding constructor and X is a union-like class,

— a non-static data member of class type M (or array thereof) that cannot be copied/moved because overload resolution (13.3), as applied to M’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,

— a direct or virtual base class B that cannot be copied/moved because overload resolution (13.3), as applied to B’s corresponding constructor, results in an ambiguity or a function that is deleted or inaccessible from the defaulted constructor,

— any direct or virtual base class or non-static data member of a type with a destructor that is deleted or inaccessible from the defaulted constructor,

— for the copy constructor, a non-static data member of rvalue reference type, or

...

3) You can declare and define (either by providing an implementation or defaulting a copy constructor, or a move copy constructor, or both.

Upvotes: 8

Michael Aaron Safyan
Michael Aaron Safyan

Reputation: 95459

Yes, that is correct. For example, if a member of the class is not copyable/assignable (e.g. a member has a private assignment operator and private copy constructor), then you won't be able to rely on a default copy constructor for the containing class. If you want the containing class to be copyable/assignable under those circumstances, then you need to define those operations explicitly.

That being said, in most cases, you should avoid passing by value. In most circumstances, you should be passing an object by constant reference or passing a smart pointer (e.g. std::unique_ptr) of your object. Passing by value (and any code that does an unnecessary amount of copying) will produce less efficient code than cases where you are able to reuse existing implementations. Additionally, for polymorphic objects, pass-by-value causes "slicing" (the functionality is truncated from the runtime type to the declared type to which the object is being copied), and so pass-by-value is particularly dangerous and error-prone when operating with any data types that might possibly be inherited.

Edit
To clarify the above a little bit... in terms of passing by const-reference vs passing by value, the decision should depend on the size of the object and how expensive it is to copy. Boost provides a handy mechanism in "call traits" (call_traits<T>::param_type) to automatically select between a value and a const reference based on the size of an object. When making this decision, it's also useful to distinguish between value types (objects that behave similarly to primitives -- for example, by overloading various operators and that are copyable, assignable, and cannot be inherited) and user-defined polymorphic types. Whenever you have a type that declares a virtual method, as a general rule of thumb, that object should be passed by reference or const reference to avoid the slicing which I mentioned above.

In terms of passing by smart pointer, generally this is done when transferring or sharing ownership (otherwise you should generally just pass around a reference to the object in question).

Upvotes: 0

Related Questions