0

I had a class with a bunch of parameters in the constructor, like this:

class Foo {
  public:
    Foo(int opt_1=0, int opt_2=100, std::string & opt_3="", bool opt_4=true);
};

Then I said, well this is getting messy, let's put all those options in a Settings class:

class Foo {
  public:
    struct Settings {
        int opt_1;
        int opt_2;
        std::string opt_3;
        bool opt_4;
        Settings() : opt_1(0), opt_2(100), opt_3(""), opt_4(true) {}
    }
    
    Foo (const Settings &settings = Settings());
};

The idea being that you set your settings and then construct the Foo object. But then how do you construct from derived classes? Let's say I have a derived class BabyFoo with specific values for the settings. Where previously I could have said:

class BabyFoo : public Foo {
 public:
   BabyFoo() : Foo(0, 500, "babyfoo", false) {}
};

How can I do this with the new Settings class?

I suppose I could have a constructor for Settings that allows all of the variables to be set, but then that defeats the whole purpose, doesn't it?

CaptainCodeman
  • 1,951
  • 2
  • 20
  • 33
  • 1
    `BabyFoo() : Foo{GetFooSettings()} {} static Foo::Settings GetFooSettings() { Foo::Settings result; result.opt_2 = 500; result.opt_3 = "babyfoo"; result.opt_4 = true; return result; }` – Eljay Apr 16 '22 at 12:02

1 Answers1

1

Disclaimer: I cannot tell for the OP, there are various possibilities dependent on the actual use case, coding conventions and needs. Below I am presenting my subjective selection to what I feel should be said in the context of the question. Again, this list is not exhaustive by any means.

To the point, in my opinion the main possibilities worth mentioning here are:

  1. Making some factory method that returns the Settings class.
  2. Using aggreate initialization.
  3. Utilizing the builder pattern.

The main catch with your code is the fact, that Settings has only default constructor. In such situation, you have to default-construct it somewhere and then pass it.

#include <string>

class Foo {
  public:
    struct Settings {
        int opt_1;
        int opt_2;
        std::string opt_3;
        bool opt_4;
        Settings() : opt_1(0), opt_2(100), opt_3(""), opt_4(true) {}
    };
    
    Foo (const Settings &settings = Settings());
};

struct BabyFoo : public Foo
{
    static Foo::Settings makeFooSettings()
    {
        Foo::Settings retval;
        retval.opt_1=1;
        retval.opt_2=22;
        retval.opt_3="abcd";
        retval.opt_4=false;
        return retval;
    }
    BabyFoo() : Foo(makeFooSettings()){}
};

Without the default constructor, the aggreate initialization can be used: Does it defeat the purpose? I don't know, you tell me that ;) With aggreate initialization last arguments can be easily omitted:

#include <string>
using namespace std::literals::string_literals;

class Foo {
  public:
    struct Settings
    {
        int opt_1 {0};
        int opt_2 {100};
        std::string opt_3{"abcd"s};
        bool opt_4{true};
    };
    
    Foo(const  Settings& s);
    Foo();
};

struct BabyFoo : public Foo
{

    BabyFoo() : Foo({8,42}){}
};

(two constructors in Foo due to: Error when using in-class initialization of non-static data member and nested class constructor) BTW, this gets even better and kinda discards the next options with C++20 designated initializers:

struct BabyFoo : public Foo
{
    BabyFoo() : Foo({.opt_4=false}){} //note that this is available since C++20
};

And if you don't like any of those solutions, you can have a look at builder pattern:

#include <string>
using namespace std::literals::string_literals;

class Foo {
  public:
    struct Settings
    {
        int opt_1 {0};
        int opt_2 {100};
        std::string opt_3{"abcd"s};
        bool opt_4{true};
        class Builder
        {
        public:
            Builder& withOpt1(int o) {o1=o; return *this;}
            Builder& withOpt2(int o) {o2=o; return *this;}
            Builder& withOpt3(const std::string& o) {o3=o; return *this;}
            Builder& withOpt1(bool o) {o4=o; return *this;}
            Settings build() const {return Settings{o1,o2,o3,o4};}
        private:
            int o1{};
            int o2{};
            std::string o3{};
            bool o4{};
        };
    };

    
    Foo(const  Settings& s);
    Foo();
};

struct BabyFoo : public Foo
{

    BabyFoo() : Foo(Foo::Settings::Builder().withOpt3("abc").build()){}
};

Note, that maybe the Builder should be building directly Foo, not its settings, but again, that up to the context.

alagner
  • 3,448
  • 1
  • 13
  • 25