Reputation: 1435
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
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:
B::filter()
B::m_aStuff
A::A(int)
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
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