user2394284
user2394284

Reputation: 6018

C++ constructor: Initialize local variable before initializer list

How to store temporary state, needed for the initializer list, in the constructor (on the stack)?

For example, implementing this constructor …

// configabstraction.h
#include <istream>

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:
    int m_x;
    int m_y;
    int m_z;
};

… using a stateful helper class like this?

// mysillyparserdontworry.h
#include <json/reader.h> //jsoncpp

class MySillyParserDontWorry
{
public:
    MySillyParserDontWorry(std::istream& input) { input >> m_parseTree; }
    int intByName(const char* name) const { return m_parseTree[name].asInt(); }

private:
    Json::Value m_parseTree;
};

My attempt:

// configabstraction.cpp

ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : local_parserState(input) // init local variable first: Not possible!
    , m_a(local_parserState.intByName("a"))
    , m_b(local_parserState.intByName("b"))
    , m_c(local_parserState.intByName("c"))
{
    MySillyParserDontWorry local_parserState; // ...because it is local
}

Upvotes: 8

Views: 4460

Answers (4)

mattnewport
mattnewport

Reputation: 14057

With C++11 you could solve this with delegating constructors:

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:
    ConfigAbstraction(const MySillyParserDontWorry& parser);

    int m_a;
    int m_b;
    int m_c;
};

ConfigAbstraction::ConfigAbstraction(const MySillyParserDontWorry& parser)
    : m_a{parser.intByName("a")}
    , m_b{parser.intByName("b")}
    , m_c{parser.intByName("c")}
{
}

ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : ConfigAbstraction{MySillyParserDontWorry{input}}
{
}

Upvotes: 10

SergeyA
SergeyA

Reputation: 62563

You can not initialize local variables bedfore members. The reason for this is very simple (so that it is NOT an artificial limitation):

Members have to be initialized (constructed) before constructor body begins, because constructor body might access them - and they need to be initialized for this access. On the other hand, local variables do not exist before code enters constructor body (as for any other function). Conclusion - initialization of local variables before members is impossible.

Upvotes: 0

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

Why not simply doing the assignments in the constructor's body then?

ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : m_a(0)
    , m_b(0)
    , m_c(0)
{
    MySillyParserDontWorry local_parserState;
    m_a = local_parserState.intByName("a");
    m_b = local_parserState.intByName("b");
    m_c = local_parserState.intByName("c");
}

Is there any specific requirement that hinders you doing that?


What an artificial limitation of C++!

It's not an artificial limitation. How should initialisation of local variables be done outside of their function's scope? It would just lead to a great confusion, where variables are actually initialised (naming conflicts aside).

Upvotes: 1

Christian Hackl
Christian Hackl

Reputation: 27528

An alternative solution to your problem would be packing the three individual ints into a common data structure. This would allow you to initialise an object of that type with a private static helper function. Being able to initialise the object instead of assigning to it later also allows for it to be const (if that is required).

Here is an example with std::tuple. But you could also create your own helper struct or even std::array<int, 3>; the basic idea remains the same: have one member object instead of three.

#include <istream>
#include <tuple>

class MySillyParserDontWorry
{
public:
    MySillyParserDontWorry(std::istream& input) { /* ... */  }
    int intByName(const char* name) const { return /* ... */ 0; }

};

class ConfigAbstraction
{
public:
    ConfigAbstraction(std::istream& input);

private:

    static std::tuple<int, int, int> parse(std::istream& input)
    {
        std::tuple<int, int, int> result;
        MySillyParserDontWorry parser(input);
        std::get<0>(result) = parser.intByName("a");
        std::get<1>(result) = parser.intByName("b");
        std::get<2>(result) = parser.intByName("c");
        return result;
    }

    std::tuple<int, int, int> const m;
};


ConfigAbstraction::ConfigAbstraction(std::istream& input)
    : m(parse(input))
{
}

Upvotes: 1

Related Questions