Disable execution of parts of the code based on user's choice C++ I am working on the small program for data acquisition where the data is written to log file and the clients can listen to data broadcasted over socket.

What I would like to achieve is to give the user some control over the program, to decide, if the data logging or the socket server should be started or not, for now I am using the console arguments for those user-program interaction. The problem is that if the user decides not to use the socket server, then multiple lines of the code in different parts of the program should not be executed (the socket object should not be constructed and non of it's methods should be executed). This easily leads to the problems, as all executions of the methods need to have the constraints. If I forget any, it leads to problems

Here you can see my actual implementation of main, which checks the user's inputs and executes code accordingly:

int main(int argc, char *argv[])
{
    std::string port = "27013"; // default port for socket, used if not specified different in console argument
    bool socketsActivated = true; // default value
    bool logActivated = true; // default value
    std::unique_ptr&lt;ServerCommunication&gt; server; // socket broadcaster object
    std::unique_ptr&lt;ServerEcho&gt; echo; // socket echoing object 
    std::unique_ptr&lt;FileOperations&gt; traceFile; // Trace logging object

    for (int i = 1; i &lt; argc; ++i) {
        if (std::string(argv[i]) == "--port") {
            if (i + 1 &lt; argc) { // Make sure we aren't at the end of argv!
                port = std::string(argv[i++]); // Increment 'i' so we don't get the argument as the next argv[i].
            }
            else { // There was no port argument
                std::cerr &lt;&lt; "--port option requires one argument." &lt;&lt; std::endl;
                return 1;
            }
        }
    }

    for (int i = 1; i &lt; argc; ++i) {
        if (std::string(argv[i]) == "--nosocket")
            socketsActivated = false;
    }

    for (int i = 1; i &lt; argc; ++i) {
        if (std::string(argv[i]) == "--nolog")
            logActivated = false;
    }

    if (socketsActivated){
        server= std::make_unique&lt;ServerEcho&gt;(port);
        echo = std::make_unique&lt;ServerEcho&gt;("27014");
    }

    if (logActivated)
        traceFile.reset(new FileOperations());

    while (!_kbhit()) // Till the key was pressed
    {
        Sleep(25); // lower the load on CPU

        if (socketsActivated){
            server-&gt;update();
            echo-&gt;update();
        }
    }
    return 0;
}


It is error-prone as if the code will get more complex additional parts of the program will need to be nested if the case requires, what will easily leads to problems...

Is there a better, easier approach for it? The preprocessor directives will not help here.
I see two things:


option acquisition (a lot of control flow and ad-hoc checks to detect the user input)
operational program flow (the decisions based on it). Here I'd suggest Null Object pattern


Acquisition

At first, I'd suggest using standard library algorithms and types instead of ad-hoc program flow and casting:

std::vector&lt;std::string&gt; const args(argv+1, argv+argc);


Which you can use much easier, like:

auto find_arg    = [&amp;](auto option) { return std::find(args.begin(), args.end(), option); };


So now you can just locate an argument using find_arg("--port"). And you can build on it:

auto find_arg    = [&amp;](auto option) { return std::find(args.begin(), args.end(), option); };
auto has_flag    = [&amp;](auto option) { return args.end() != find_arg(option); };


Now you can simply do:

bool const socketsActivated = !has_flag("--nosocket");
bool const logActivated     = !has_flag("--nolog");


Note that these are immutable, so program logic is strictly simpler and more verifiable. Getting the port takes a little more effort, but can be supported generically too:

auto get_option  = [&amp;](auto option, auto default_value) -&gt; std::string {
    auto it = find_arg(option);
    if (it != args.end()) {
        it = ++it;
        if (it == args.end()) { // There was no argument
            throw std::runtime_error(std::string(option) + " requires an argument");
        }
        return *it;
    }
    return default_value;
};


So now you can write:

int main(int argc_, char *argv_[])
{
    std::vector&lt;std::string&gt; const args(argv_+1, argv_+argc_);

    auto find_arg    = [&amp;](auto option) { return std::find(args.begin(), args.end(), option); };
    auto has_flag    = [&amp;](auto option) { return args.end() != find_arg(option); };
    auto get_option  = [&amp;](auto option, auto default_value) -&gt; std::string {
        auto it = find_arg(option);
        if (it != args.end()) {
            it = ++it;
            if (it == args.end()) { // There was no argument
                throw std::runtime_error(std::string(option) + " requires an argument");
            }
            return *it;
        }
        return default_value;
    };

    std::string const port      = get_option("--port", "27013");
    bool const socketsActivated = !has_flag("--nosocket");
    bool const logActivated     = !has_flag("--nolog");


It's more legible, less error prone and very extensible.

The extra mile

When you find yourself collecting operations like has_flag, get_option you can move them to a suitable abstraction.

Here's exactly the same data + operations grouped into a CommandLineParser:

struct CommandLineParser {
    CommandLineParser(int argc, char** argv) : _args(argv+1, argv+argc) {}

    auto find_arg  (std::string const&amp; option) const {
        return std::find(_args.begin(), _args.end(), option);
    };
    bool has_flag  (std::string const&amp; option) {
        return _args.end() != find_arg(option);
    };
    std::string get_option(std::string const&amp; option, std::string const&amp; default_value) {
        auto it = find_arg(option);
        if (it != _args.end()) {
            it = ++it;
            if (it == _args.end()) { // There was no port argument
                throw std::runtime_error(option + " requires an argument");
            }
            return *it;
        }
        return default_value;
    };

    std::vector&lt;std::string&gt; const _args;
};


And now main starts like this:

int main(int argc, char *argv[])
{
    CommandLineParser clp(argc, argv);
    std::string const port      = clp.get_option("--port", "27013");
    bool const socketsActivated = !clp.has_flag("--nosocket");
    bool const logActivated     = !clp.has_flag("--nolog");


Clean as a whistle.

Null Object Pattern

Here you prevent having null objects or repeated switches by having different implementations of the same interface. You just instantiate a "do-nothing" implementation if the behaviour is disabled:

/////////////////////////////////////
// abstract interface and null-implementation
struct IServer {
    virtual void update() = 0;
};

struct NullServer : IServer {
    virtual void update() override { }; // do nothing
};

struct CommunicationServer : IServer {
    CommunicationServer(std::string) {}
    virtual void update() override {
        std::clog &lt;&lt; "Communication update\n";
    }
};

struct EchoServer : IServer {
  EchoServer(std::string) {}
    virtual void update() override {
        std::clog &lt;&lt; "Echo update\n";
    }
};


Now you can simplify the call - site:

int main(int argc, char *argv[])
{
    std::unique_ptr&lt;IServer&gt;          server(new NullServer);
    std::unique_ptr&lt;IServer&gt;          echo(new NullServer);
    std::unique_ptr&lt;IFileOperations&gt;  traceFile(new NullFileOperations);

    // parse user config
    CommandLineParser clp(argc, argv);

    if (!clp.has_flag("--nosocket")){
        server = std::make_unique&lt;CommunicationServer&gt;(clp.get_option("--port", "27013"));
        echo   = std::make_unique&lt;EchoServer&gt;("27014");
    }

    if (!clp.has_flag("--nolog")) {
        traceFile = std::make_unique&lt;FileOperations&gt;();
    }

    while (!_kbhit()) // Till the key was pressed
    {
        Sleep(25); // lower the load on CPU

        server-&gt;update();
        echo-&gt;update();
    }
}


Full Demo

Live On Coliru

#include &lt;string&gt;
#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;
#include &lt;memory&gt;
#include &lt;thread&gt;

// mocks
static void Sleep(int ms) {
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}

static bool _kbhit() {
    static int remain = 10;
    return remain-- == 0;
}

/////////////////////////////////////
// abstract interface and null-implementation
struct IServer {
    virtual void update() = 0;
};

struct NullServer : IServer {
    virtual void update() override { }; // do nothing
};

struct CommunicationServer : IServer {
    CommunicationServer(std::string) {}
    virtual void update() override {
        std::clog &lt;&lt; "Communication update\n";
    }
};

struct EchoServer : IServer {
  EchoServer(std::string) {}
    virtual void update() override {
        std::clog &lt;&lt; "Echo update\n";
    }
};


/////////////////////////////////////
// similar for other interfaces
struct IFileOperations { };
struct NullFileOperations : IFileOperations { };
struct FileOperations : IFileOperations {};

struct CommandLineParser {
    CommandLineParser(int argc, char** argv) : _args(argv+1, argv+argc) {}

    auto find_arg  (std::string const&amp; option) const {
        return std::find(_args.begin(), _args.end(), option);
    };
    bool has_flag  (std::string const&amp; option) {
        return _args.end() != find_arg(option);
    };
    std::string get_option(std::string const&amp; option, std::string const&amp; default_value) {
        auto it = find_arg(option);
        if (it != _args.end()) {
            it = ++it;
            if (it == _args.end()) { // There was no port argument
                throw std::runtime_error(option + " requires an argument");
            }
            return *it;
        }
        return default_value;
    };

    std::vector&lt;std::string&gt; const _args;
};

int main(int argc, char *argv[])
{
    std::unique_ptr&lt;IServer&gt;          server(new NullServer);
    std::unique_ptr&lt;IServer&gt;          echo(new NullServer);
    std::unique_ptr&lt;IFileOperations&gt;  traceFile(new NullFileOperations);

    // parse user config
    CommandLineParser clp(argc, argv);

    if (!clp.has_flag("--nosocket")){
        server = std::make_unique&lt;CommunicationServer&gt;(clp.get_option("--port", "27013"));
        echo   = std::make_unique&lt;EchoServer&gt;("27014");
    }

    if (!clp.has_flag("--nolog")) {
        traceFile = std::make_unique&lt;FileOperations&gt;();
    }

    while (!_kbhit()) // Till the key was pressed
    {
        Sleep(25); // lower the load on CPU

        server-&gt;update();
        echo-&gt;update();
    }
}


Which, without options, prints e.g.

Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update
Communication update
Echo update


But with --nosocket prints nothing, and with missing port prints:

terminate called after throwing an instance of 'std::runtime_error'
  what():  --port requires an argument