Search code examples
c++c++11constructordefault-parameters

What is the preferable way of initializing default arguments of a constructor?


I have seen following two ways of default initializing the arguments in constructors(which is also applicable in normal free functions too).

#include <string>
using UserDefinedType = std::string;

class MyClass
{
    UserDefinedType m_member;
public:
    // Way - 1
    MyClass(const UserDefinedType &obj = UserDefinedType()) : m_member{ obj } {}
    // Way - 2
    //MyClass(const UserDefinedType &obj = {}) : m_member{ obj }  {}
};
  • I know the first will defensively(explicitly) call the constructor of user defined type. What happens in the second way?
  • Which is the preferable way of practicing with modern compilers(C++11 or later) ?

Solution

  • This is a matter of personal preference, there are no implications on what these two options do or invoke. As I guess it is common sense to not repeatedly type types as in

    const int three = static_cast<int>(3.14);
    const Sub* sub = dynamic_cast<Sub*>(&baseInstance);
    

    which is often written down with auto as

    // better, the concrete type is only typed once, less maintainance "burden":
    const auto three = static_cast<int>(3.14);
    const auto* sub = dynamic_cast<Sub*>(&baseInstance);
    

    you could take this argument and transfer it to the example snippet above:

    MyClass(const UserDefinedType &obj = UserDefinedType());
    

    Here, the type is spelled out twice, and that's undesirable. Hence, I recommend going with

    // Shorter, not less readable - a default-constructed default instance:
    MyClass(const UserDefinedType &obj = {})
    

    Note that in the special case of constructors, it is equally simple to use in-class member initialization together with a default constructor and an additional overload, e.g.

    MyClass {
      public:
        MyClass() = default;
        explicit MyClass(const UserDefinedType& obj) : m_member{obj} {}
    
      private:
        UserDefinedType m_member = {};
    };
    

    The advantage of this approach is the low likelihood of introducing bugs when a new constructor overload is added to the class. But this is a nuance. Note, however, that I've marked the single-argument ctor explicit, which is usually considered good practice to prevent accidental implicit conversions.