Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are defaulted methods and how do I use them properly? [duplicate]

Tags:

c++

c++11

Possible Duplicate:
What’s the point in defaulting functions in C++11?

C++11 introduced defaulted methods (e.g. void myMethod() = default;).

What does it do to methods (how do methods behave after being defaulted). How do I use them properly (what are its uses)?

like image 856
Mark Garcia Avatar asked Dec 01 '12 00:12

Mark Garcia


1 Answers

There are a number of class members that are considered "special member functions" by the C++ standard. These are:

  • The default constructor (a constructor that can be called with no parameters).
  • The copy constructor (a constructor that can be called with one parameter that is the object type as an lvalue reference).
  • The copy assignment operator (an operator= overload that can be called with one parameter that is the object type as either an lvalue reference or a value).
  • The move constructor (a constructor that can be called with one parameter that is the object type as an rvalue reference).
  • The move assignment operator (an operator= overload that can be called with one parameter that is the object type as either an rvalue reference or a value).
  • The destructor.

These member functions are special in that the language does special things with them on types. Another thing that makes them special is that the compiler can provide definitions for them if you do not. These are the only functions that you can use the = default; syntax on.

The issue is that the compiler will only provide a definition under certain conditions. That is, there are conditions under which a definition will not be provided.

I won't go over the entire list, but one example is what others have mentioned. If you provide a constructor for a type that is not a special constructor (ie: not one of the constructors mentioned above), a default constructor will not be automatically generated. Therefore, this type:

struct NoDefault
{
  NoDefault(float f);
};

NoDefault cannot be default constructed. Therefore, it cannot be used in any context where default construction is needed. You can't do NoDefault() to create a temporary. You can't create arrays of NoDefault (since those are default constructed). You cannot create a std::vector<NoDefault> and call the sizing constructor without providing a value to copy from, or any other operation that requires that the type be DefaultConstructible.

However, you could do this:

struct UserDefault
{
  UserDefault() {}
  UserDefault(float f);
};

That would fix everything, right?

WRONG!

That is not the same thing as this:

struct StdDefault
{
  StdDefault() = default;
  StdDefault(float f);
};

Why? Because StdDefault is a trivial type. What does that mean? I won't explain the whole thing, but go here for the details. Making types trivial is often a useful feature to have, when you can do it.

One of the requirements of a trivial type is that it does not have a user-provided default constructor. UserDefault has a provided one, even though it does the exact same thing as the compiler-generated one would. Therefore, UserDefault is not trivial. StdDefault is a trivial type because = default syntax means that the compiler will generate it. So it's not user-provided.

The = default syntax tells the compiler, "generate this function anyway, even if you normally wouldn't." This is important to ensure that a class is a trivial type, since you cannot actually implement special members the way the compiler can. It allows you to force the compiler to generate the function even when it wouldn't.

This is very useful in many circumstances. For example:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

private:
  std::unique_ptr<SomeType> m_ptr;
};

We must write every one of these functions to make the class copy the contents of the unique_ptr; there's no way to avoid that. But we also want this to be moveable, and the move constructor/assignment operators won't automatically be generated for us. It'd silly to re-implement them (and error-prone if we add more members), so we can use the = default syntax:

class UniqueThing
{
public:
  UniqueThing() : m_ptr(new SomeType()) {}
  UniqueThing(const UniqueThing &ptr) : m_ptr(new SomeType(*ptr)) {}
  UniqueThing(UniqueThing &&ptr) = default;
  UniqueThing &operator =(const UniqueThing &ptr)
  {
    m_ptr.reset(new SomeType(*ptr)); return *this;
  }

  UniqueThing &operator =(UniqueThing &&ptr) = default;

private:
  std::unique_ptr<SomeType> m_ptr;
};
like image 127
Nicol Bolas Avatar answered Sep 23 '22 08:09

Nicol Bolas