Class destructor invocation - possible by hand?
I have a question for all you C++ gurus. I'm writing a class that I would like to use the following technique in, and I'm not sure if it's portable or even legal, although VC++6 and VC++.NET, which I'm compiling in, work beautifully with it. Consider the following class, a very simplified example that I wrote to demonstrate: class House { public: explicit House(const int rooms) : m_rooms(rooms) { } ~House() { } private: const int m_rooms; }; Pretty straightforward. I'd like m_rooms to remain const to protect it from accidental modification by member functions, since it will *never* be changed during operation. Now, the first problem that arises is that this is illegal: House housingComplex[100]; Because, obviously, the constructor requires that the number of rooms be passed to it. (Yes, we could create it as an array of House pointers and create them dynamically, but in my case that will require inconveniences by everybody using my class.) So, we make it a default parameter and add an assignment operator: class House { public: explicit House(const int rooms = -1) : m_rooms(rooms) { } ~House() { } House& operator =(const House& source); private: const int m_rooms; }; When rooms == -1, the House is considered to be uninitialized. Now, we can do the following: House housingComplex[100]; housingComplex[0] = House(5); housingComplex[1] = House(4); (etc) The question is, how to implement the assignment operator? This is my method: House& House::operator =(const House& source) { assert(m_rooms == -1); this->~House(); new(this) House(source); return *this; } Yikes! I know this looks weird. First, assert verifies in debug mode that the object is in its uninitialized state. Then, the destructor is called manually to destruct the class and its contents. (In the actual class I'm working on, there are numerous member variables that must be appropriately destructed, and all of them are - I've checked.) Simply calling ~House(); will not compile, because the compiler assumes I'm trying to use the ~ operator illegally, so I have to call it through the 'this' pointer. Then, the placement new operator is used to create a new House in exactly the same space in memory as the old one, using the default copy constructor so that all the const variables are (re)initialized. Finally, a technicality for all assignment operators, a reference to the object is returned. (This is so C-style expressions like "var1 = var2 = var3 = 5;" can be used.) Now, this works beautifully on my VC++. But... is it legal C++?? Am I entering the realm of "undefined behavior", or is this a perfectly legitimate technique? Is there an obviously better solution to my problem? Are there caveats that I should be looking out for? Please, someone with more knowledge of the C++ standards, give me a hand here. Thanks! -Jonathan
House& House::operator =(const House& source) { assert(m_rooms == -1); this->~House(); new(this) House(source); return *this; }
I looked at the C++ FAQ and I was surprised not to find this mentioned in there. This "technique" is generally regarded as poor technique and potentially dangerous by the "gurus". It is not exception safe: If the copy constructor fails due to an exception, then the "this" object will be left in a destroyed state. It will probably be destroyed again when your object leaves scope. Destroying an object twice is undefined behavior. If you derive off of your "house" object, then this assignment operator will slice any of the derived parts... In general, assignment operator should look like this (WARNING: untested code) House& House::operator =(const House& source) { House temp(source); Swap(temp); return *this; } The Swap() function is a member function that has "No-Throw" specification. It swaps out the internals of the *this instance with the "temp" instance. Using this method, you are still writing your assignment operator in terms of your copy constructor, so you don't need to duplicate work. And this method is strongly exception safe; if the copy constructor throws, then the current object is unchanged. Also, I would recommend that you either reconsider having the constant member variable, or use a vector<>. Johan __________________________________________________ Do You Yahoo!? Send your FREE holiday greetings online! http://greetings.yahoo.com
House& House::operator =(const House& source) { assert(m_rooms == -1); this->~House(); new(this) House(source); return *this; } It is not exception safe: If the copy constructor fails due to an exception, then the "this" object will be left in a destroyed state. It will probably be destroyed again when your object leaves scope. Destroying an object twice is undefined behavior.
Good point.
If you derive off of your "house" object, then this assignment operator will slice any of the derived parts...
Assignment operators aren't inherited, though, so if I derive a new class, say, Mansion, this would be illegal rather than slice off derived parts: Mansion x, y; x = y; Mansion would have to define its own assignment operator, if needed. (A client could hypothetically cast a Mansion to a House& and use its assignment operator, but no right-minded C++ programmer should resort to something that flagrant, and it would fail rather obviously if the object is used afterwards.)
In general, assignment operator should look like this (WARNING: untested code) House& House::operator =(const House& source) { House temp(source); Swap(temp); return *this; }
The Swap() function is a member function that has "No-Throw" specification. It swaps out the internals of the *this instance with the "temp" instance.
This makes sense, and would work for nearly all other types of classes.
But, is there any way to implement Swap so that it can swap out const
data members? I certainly can't do something like:
House* temp = new House(source);
memcpy(this, temp, sizeof(House);
// don't call original destructor
delete static_cast
Using this method, you are still writing your assignment operator in terms of your copy constructor, so you don't need to duplicate work. And this method is strongly exception safe; if the copy constructor throws, then the current object is unchanged.
Also, I would recommend that you either reconsider having the constant member variable, or use a vector<>.
Using a vector<T> won't work in this case, because the STL uses T::operator = in order to copy vector contents into new locations during memory reallocation. And there's no logical way to implement operator = when a const data member is present, which is the cause of this conversation. I could use std::list, since it doesn't do reallocation, but clients using this class in containers will most likely need random access iterators. Any other ideas? -Jonathan
----- Original Message -----
From: "Jonathan Brownell"
Essentially what I need is a const data member that is protected as a const except during House::operator =(const House&), at which point the entire class is replaced. Am I just out of luck? If so, this makes const class data members pretty useless in many cases, because similar situations to this have arisen quite a few times for me.
You could use Johan Ericsson idea:
House& House::operator =(const House& source) { House temp(source); Swap(temp); return *this; }
and then use const_cast in swap:
void swap(const House& h)
{
int temp = h.m_rooms;
override_const_assign(h.m_rooms, m_rooms); // h.m_rooms = m.rooms
override_const_assign(m_rooms, temp); // m_rooms = temp;
}
template<class T>
void override_const_assign(const T& lhs, const T& rhs)
{
lhs2 = const_cast
Also, I would recommend that you either reconsider having the constant member variable, or use a vector<>.
Using a vector<T> won't work in this case, because the STL uses T::operator = in order to copy vector contents into new locations during memory reallocation. And there's no logical way to implement operator = when a const data member is present, which is the cause of this conversation. I could use std::list, since it doesn't do reallocation, but clients using this class in containers will most likely need random access iterators.
Any other ideas?
Easy option: Use a vector
I second Georges recommendation about using shared pointers.
I would say that the problem you described falls into the category of
how to use
STL for classes which are not STL compatable because of the way they
copy or construct, etc.
Really it is confusing for people starting up with STL, and it is never
covered very
well in documentation, i.e., clear warning about what you shouldn't put
in a vector.
vector
Also, I would recommend that you either reconsider having the constant member variable, or use a vector<>.
Using a vector<T> won't work in this case, because the STL uses T::operator = in order to copy vector contents into new locations during memory reallocation. And there's no logical way to implement operator = when a const data member is present, which is the cause of this conversation. I could use std::list, since it doesn't do reallocation, but clients using this class in containers will most likely need random access iterators.
Any other ideas?
Easy option: Use a vector
. This will give you basically the behavior you want, with the ability to initialize the elements directly. The drawback is that the data will be dynamically allocated.
[Non-text portions of this message have been removed]
At 1:44 PM -0700 1/16/02, Jonathan Brownell wrote:
I have a question for all you C++ gurus.
This question (and this thread) is valid and interesting, but off-topic. If you wish to continue it, please do so off line. The purpose of this forum is to discuss boost library usage. While threads will no doubt wander off topic on occasion, this forum will lose some of its value if we allow it to become a general C++ discussion list (as interesting and valuable as that might be). I am certain that good C++ discussion forums exist. I would invite members of this list to send me information on these alternatives and I will summarize either by adding links to the Group's bookmark section http://groups.yahoo.com/group/Boost-Users/links, or adding an HTML file of links to the Group's shared files area http://groups.yahoo.com/group/Boost-Users/files/. Thanks you. -- Jon Kalb Boost-User Moderator Kalb@LibertySoft.com
participants (6)
-
George A. Heintzelman
-
Gustavo Guerra
-
hicks
-
Johan Ericsson
-
Jon Kalb
-
Jonathan Brownell