TITLE: Problems with "placement" form of operator = PROBLEM: rmartin@rcmcon.com (Robert Martin), 21 Jan 94 There have been several postings about the potential for using placement syntax in assignment operators as follows: X& X::operator=(const X& x) { if (this != &x) { this->X::~X(); // destruct new (this) X(x); // copy construct in place. } return *this; } While this model seems compelling, there appear to be several significant dangers. Consider the following program: ---------------------------------------------------------------------------- #include #include void* operator new (size_t, void* p) {return p;} class B { public: B(int i) : id(i) {} B(const B& b) : id(b.id) {} B& operator=(const B&); virtual ~B() {cout << this << ":" << *this << "is destroyed" << endl;} void Identify() {cout << this << ": points at " << *this << endl;} virtual ostream& Print(ostream&) const; int GetID() const {return id;} friend ostream& operator<<(ostream& o, const B& b) {return b.Print(o);} private: int id; }; B& B::operator=(const B& b) { if (this != &b) { this->B::~B(); new (this) B(b); } return *this; } ostream& B::Print(ostream& o) const { o << "B(" << id << ")"; return o; } class D : public B { public: D(int i, int j) : B(i), id2(j) {} D(const D& d) : B(d), id2(d.id2) {} D& operator=(const D&); virtual ~D() {cout << this << ":" << *this << "is destroyed" << endl;} virtual ostream& Print(ostream&) const; private: int id2; }; D& D::operator=(const D& d) { if (this != &d) { this->D::~D(); new (this) D(d); } return *this; } ostream& D::Print(ostream& o) const { o << "D(" << GetID() << ',' << id2 << ")"; return o; } main() { B b(1); D d(2,2); b.Identify(); d.Identify(); B& b2 = d; b2 = b; // change type of d to B!!!!! b.Identify(); d.Identify(); } ---------------------------------------------------------------------- gnu 2.5.7 produces the following output (which I have annotated.) 0xbffffdc0: points at B(1) 0xbffffdb4: points at D(2,2) <---------- 'd' 0xbffffdb4:B(2)is destroyed <---------- Destructor not virtually deployed. 0xbffffdc0: points at B(1) 0xbffffdb4: points at B(1) <---------- 'd' 0xbffffdb4:D(1,2)is destroyed <---------- Destructor not virtually deployed. 0xbffffdb4:B(1)is destroyed 0xbffffdc0:B(1)is destroyed Although there are several interesting anomalies to consider, the one at issue is the fact that the D object contained by the variable 'd' has had its type changed to B; i.e. virtual deployment through 'd' now only accesses functions in B. Clearly this is very dangerous since the static type of 'd' is still D, and other parts of the program will treat it as such. While it is interesting that you can use this form of assignment to change the type of an object, it is also pretty scary. Other features of interest in the above output involve the handling of destructors. Even though the destructors for B and D are virtual, the destructor call from the assignment operator was not virtually deployed. This is not quite what I expected, and I could be convinced that it was a bug in gcc, however it does appear consistent with the ARM's restriction that destructors can only be explicitly called by qualified name. Perhaps the intent is that virtual deployment of destructors only takes place at 'deletion' and not simple 'destruction'. Another case of "aberant destruction" occurs when 'd' goes out of scope. At this time, the object in d is "virtually" only a B, and yet D::~D() is being called. So again, it appears that virtual deployment of destructors is reserved for 'delete'. ------------------------------------------- The fact that one can use this kind of assignment operator to change the vtbl of an object is interesting. Consider what might happen if this idiom were employed in MI. A / B C \ / D D d; A& a = d; // legal upcast A x; a = x; // Does this change D::C to A? This seems likely, not just to replace C's vtbl with one for A, but to corrupt it as well. At least in some implementations, offsets for MI are stored in the vtbl. These offsets will be destroyed when the vtbl is replaced; yeilding undefined behaviors.