turoni
turoni

Reputation: 1435

constructor initialization function member instantation

MCVE

#include <map>
class A{
public:
    A(int){

    }
};

class B : public A{
public:
    B()
     : A(filter()){}
    int filter(){
        std::map<int,int> aStuff;
        //new(&m_aStuff)std::map<int,int>;
        m_aStuff = aStuff;
        return 0;
    }
private:
    std::map<int,int> m_aStuff;
};

int main(){
    B b;
    return 0;
}

This fails at compile time because m_aStuff is not initialized. Using answer I added

new(&m_aStuff)std::map<int,int>;

If you uncomment that line it runs at compile time but m_aStuff gets re-initialized anyway when you leave the filter class.

Upvotes: 1

Views: 78

Answers (2)

Teivaz
Teivaz

Reputation: 5665

As your unasked question "How do I fix it?" was answered by Yuki without any details I will provide an answer "What happend there?".

In C++ object is constructed from base class. So the order is following:

A::A(int); // base class constructor
B::B(); // inherited class constructor

All member objects are initialized in initializer list:

B::B()
: // Initializer list begin.
A(), // Base class initialized first.
m_aStuff() // Members of current class are initialized next.
{ // End of initializer list.
    // All members are safe to use.
}

This is the order of object initialization. As you can see here the member m_aStuff initialized after base constructor A::A(int). Constructor is a member function and as every function requires arguments to be evaluated. So the function int B::filter() is called before object is initialized. Which means that member variables are not initialized either. In other words the order of execution is:

  1. Call B::filter()
  2. Modify member variable B::m_aStuff
  3. Call base class constructor A::A(int)
  4. Initialize member variable B::m_aStuff

Obviously step 2 is modifying variable before it was initialized. Depending on implementation of std::map this can cause different behavior (probably undefined).

In fact following two constructions are equal:

B::B() :
A(0)
{}

and

B::B() :
A(0),
m_aStuff()
{}

but in the second case you are initializing it explicitly, while in first case compiler will generate this code for you.

Your solution new(&m_aStuff)std::map<int,int>; initializes object before using it which makes behavior more defined. But next the generated constructor of class B will kick in and B::m_aStuff will be initialized once again. This will set your map to initial state (though I can imagine scenarios in which memory will leak).

Upvotes: 1

Yuki
Yuki

Reputation: 4163

I would strongly recommend you to reconsider the design, but here is my solution:

class A {
public:
  A(int) {}
};

class B : public A {
public:
  struct Dirty {
    std::map<int, int> map;
  };

  B(Dirty dirt = Dirty()) : A(filter(dirt)), m_aStuff(std::move(dirt.map)) {}
  int filter(Dirty& dirt) {
    std::map<int, int> aStuff;
    //new(&m_aStuff)std::map<int,int>;
    dirt.map = aStuff;
    return 0;
  }

private:
  std::map<int, int> m_aStuff;
};

int main() {
  B b;
  return 0;
}

Nicer solution:

class B : public A {
public:
  B() : B(filter()) {}

private:
  B(std::tuple<int, std::map<int, int>> t)
    : A(std::get<0>(t)), m_aStuff(std::move(std::get<1>(t))) {}

  static std::tuple<int, std::map<int, int>> filter() {
    std::map<int, int> aStuff;
    //new(&m_aStuff)std::map<int,int>;
    return make_tuple(0, aStuff);
  }    

  std::map<int, int> m_aStuff;
};

Upvotes: 1

Related Questions