1

I have a class with a default constructor which initializes many member variables via initializer lists.

I wish to have a parametrized constructor which only updates one of the member variables. A simple example is below, with name being the only variable that I would like to initialize via the default constructor.

Does it make sense to repeat the initializer list the same as for the default constructor here? Or can I call the default constructor from the parameterized constructor first?

What is the most efficient way to do this?

#include <iostream>
#include<iostream>
#include<fstream>

using namespace std;

class MyClass
{
    public:
        MyClass() : name("John"),
                    age(20),
                    distance(10)
                    // Many more default initializers
        {

        }

        MyClass(string name)
        {
            // How to only initialize name with the parameter passed, while everything else still gets the default value like in the default constructor. 

        }

        ~MyClass()
        {

        }

    private:
        string name;
        int age;
        int distance;
        // Many more 
};
5
  • "// How to only initialize name" - this will be a bug. The values must not be left uninitialized. Commented Mar 26 at 21:52
  • @3CxEZiVlQ No, I mean only initialize name with a parameter from the paramaterized constructor. Everything else still should get the default value as per the default constructor. Commented Mar 26 at 21:56
  • What you want to get in the constructor initializer list is impossible - we can't re-initialize member variables after the default constructor. Commented Mar 26 at 21:58
  • Read about Delegating constructor. Commented Mar 27 at 0:10
  • Builder pattern might interest you too. Commented Mar 27 at 8:57

3 Answers 3

6
// How to only initialize name`

This will be a bug. Other values must not be left uninitialized.

Perhaps, you wanted

#include <string>
#include <utility>

class MyClass {
 public:
  MyClass() : MyClass("John") {} // delegate to the ctor below

  explicit MyClass(std::string name) : name(std::move(name)), age(20), distance(10) {}

  ~MyClass() = default;

 private:
  std::string name;
  int age;
  int distance;
};

Or

#include <string>
#include <utility>

class MyClass {
 public:
  MyClass() = default;

  explicit MyClass(std::string name) : name(std::move(name)){}

  ~MyClass() = default;

 private:
  std::string name = "John";
  int age = 20;
  int distance = 10;
};
Sign up to request clarification or add additional context in comments.

2 Comments

I'm a big fan of the second option. Simple and foolproof.
Thanks for this suggestion. What is the benefit of using std::move in the first option here? If we call the default contructor, name gets initialized with "John". If we call the second constructor, name gets initialized with name. Why the need for move?
1

There are different ways to accomplish this:

  • Define multiple constructors

#include<iostream>
#include<fstream>

using namespace std;

class MyClass
{
    public:
        MyClass() : MyClass("John", 20, 10)
        {

        }

        MyClass(string name): MyClass(name, 20, 10)
        {

        }

        ~MyClass()
        {

        }

        MyClass(std::string pName, int pAge, int pDistance):
            name(pName),
            age(pAge),
            distance(pDistance)
            {}

    private:
        string name;
        int age;
        int distance;
        // Many more 
}; 
  • Initialize private members

    #include<iostream>
    #include<fstream>
    
    using namespace std;
    
    class MyClass
    {
        public:
            MyClass()
            {
                // using the default values 
            }
    
            MyClass(string pName): name(name)
            {
                // update the value of name and keep other set to default
            }
    
            ~MyClass()
            {
    
            }
    
            MyClass(std::string pName, int pAge, int pDistance):
                name(pName),
                age(pAge),
                distance(pDistance)
                {
                    // if needed, you can have another constructor to change the default values
                }
    
        private:
            string name = std::string("default name");
            int age = 0;
            int distance = 0;
            // Many more 
    };
    

The optimum solution depends on your requirements/usage.

For example, if you go for the second option, any change in the default values will trigger a rebuild on all files including the class definition.

Comments

1

My preferred method is aggregate builder pattern:

class MyClass {
public:
    struct my_init{
    //Aggregate builder class
        std::string name = "John";
        int age = 20;
        int distance = 10;
    };

    MyClass() = default;

    explicit MyClass(my_init val)
    : my{std::move(val)} {};

    ~MyClass() = default;

private:
    my_init val;
};

MyClass my_obj0;

//double brackets is the cost:
MyClass my_obj1 {{ .distance=50 }};

This pattern uses named parameters through designated initializer list of aggregate classes. If MyClass members have differing access specifiers, then a little more complexity is introduced into the class:

class MyClass {
public:
    //Aggregate builder class:
    struct my_init;//same as above

    MyClass()
    //delegating constructor call:
    : MyClass{my_init{}} {};

    explicit MyClass(my_init val)
    : name{std::move(val).name}
    , age{std::move(val).age}
    , dist{std::move(val).distance}
    {/*ctor*/};

    ~MyClass() = default;

private:
    std::string name;
protected:
    int age;
    int dist;
};

This is still much simpler than JAVA style builder pattern.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.