Well, this is it - the final blog post. And you know what, in spite of having written so many critical posts earlier, I have to say I feel good having taken this course.
Of course, I still stick to my previous argument that the textbook should not simply be left for weekend reading. A lot of core object oriented concepts got relegated to the 2-minute quizzes, and I feel this course would be more true to its title if it discussed those issues. Even if there isn't all that much time to discuss them in class, the "object-oriented" projects should be brought on earlier, and projects like "Grades" should be modified so that the emphasis is on the object-oriented nature of the system we're trying to model and not on printing tables. Prof. Downing might even consider the idea of having one massive project substitute the last 2-3 projects (after all one of the main concepts of object-oriented programming is to make huge complex projects manageable)
Sure, if you look at it from the narrow day-to-day perspective, it may seem trivial; but when I look at the course as a whole - I feel it has been a worthwhile experience. Do I know more about C++ than when I started out - yes. Am I more confident about better software design and engineering practices - yes. Will all the tiny nuances about the language come together to help me write better code - definitely! The pair programming has also helped me - I was quite skeptical initially since I prefer not to irritate someone with my obsessive compulsive needs for bracing styles etc. But as the projects progressed, I got more and more comfortable working with my project partners - and it's a huge weight off your shoulders when you can do that. If anything, just the experience of collaborating so closely with someone on a project was worth the time and effort spent on this course.
CS 371p: Object-Oriented Programming Weekly Blog
Sunday, May 6, 2012
Monday, April 30, 2012
#14 April 29
This week was entirely spent on learning about Handle classes in C++. This is a pretty interesting concept, and even more so, since the paper I reviewed a couple of weeks back is about smart pointers in C++ which can manage an underlying raw pointer - which is almost exactly what the Handle classes do.
We studied three different kinds of Handle classes - going from the simple Handle1, which is a very basic wrapper which doesn't even have support for de-referencing or indirection - implying that all the public API of the object of the underlying pointer will have to be replicated. The advantage, though is that the Handle1 objects can be looked upon as (polymorphic) objects and not pointers. A slightly more sophisticated version - Handle2 - overloads the de-reference and indirection pointers, thus saving us the trouble of replicating the public API in the handler class. But we lose the ability to look at Handle2 as an object - and it is now very clearly a pointer to the objects we want to use, however with memory management being handled internally. This resembles the unique_ptr in the C++ standard. It's also interesting to notice how the copy constructor works for these classes. Since they contain a pointer, we cannot simply copy over the pointer to the newly constructed class, because this could lead to delete being called twice on the same object. There are 2 solutions - one in which strict ownership is maintained, and another in which the object itself is copied. In the first one, the raw pointer "moves" to the newly constructed Handle, while the one in the old Handle is made NULL. If we want to keep using the old Handle, we can use the 2nd solution in which the pointer is cloned (the object it points to is copied, and the new pointer points to this new object). The final version of Handle is similar to C++'s shared_ptr. It uses reference counting so that multiple pointers could point to the same object and delete is called only when the last pointer to the object is deleted.
We used a variation of Handle1 for the Cell class in the project Life - and it allowed us to have a heterogeneous cell on the grid. By wrapping an underlying AbstractCell pointer, it allowed us to use polymorphism to have either a FredkinCell object or a ConwayCell object at a grid location.
Sunday, April 22, 2012
#13 April 22
The 6th project is under our belts...and the only hurdle between me and sunny freedom is Test 3. We'll cross that bridge later.
The final project was based on Conway's game of life, but with a slight variation of the cells - Fredkin cells, also in the fray. I think the whole point of the project was to appreciate inheritance and abstract base classes and how polymorphism could be used to construct generic heterogenous structures. It was also interesting because I just reviewed a paper on smart pointers, and the Cell class that we were writing was similar to one - a wrapper around a raw pointer whose memory management was being dealt with internally. It's interesting to write the copy-assignment and copy constructors for such classes, because you have to be very careful about strict ownership of the raw pointer(so that delete is called only once) and other possibilities of memory leaks.
The code for the FredkinCell and the ConwayCell in itself was not hard to do at all - especially since my partner and I spent a good amount of time thinking about the design (we did not give in to the temptation of writing monolithic un-maintainable monster functions to avoid unit tests - though it is kind of ambiguous whether you should write unit tests for every implementation of shared functions). Our initial design was the simple 1-pass update, in which every cell - dead or alive - was checking its neighbors and updating its state accordingly. However, when we ran the ConwayCell case using valgrind - this was taking nearly 8 minutes to run! There was an obvious need for re-design! So we re-designed it so that only the live cells would be checked and its neighbors' neighbor counts would be updated. Much faster, but wait! There's havoc on the generic Cell board. A FredkinCell won't update a ConwayCell which is diagonally its neighbor, and a ConwayCell will try to update the neighbor count of a diagonally placed FredkinCell whether it likes to or not! The realization was that we had to handle orthogonal neighbors (the kind that FredkinCells recognize) and diagonal neighbors differently. With that tweak, and some optimizations as far as vector handling was concerned, our run time using valgrind dropped to ~6s! And without the overhead of valgrind's checks, it runs in less than a second. Now that's what we're talking about!
Another interesting issue that I'm glad to have learned is the concept of the virtual destructor. If you get used to Java, you forget the dangers of polymorphism being turned OFF by default. One of these is the innocuous fact that the free destructors the compiler generates for you in C++ can be dangerous when it comes to inheritance hierarchies. If you assign a derived class object to a base pointer and then call delete on it, unless the base destructor has been marked virtual - this will cause undefined behavior (the base destructor will be invoked, not the derived one)! You have been warned!
The final project was based on Conway's game of life, but with a slight variation of the cells - Fredkin cells, also in the fray. I think the whole point of the project was to appreciate inheritance and abstract base classes and how polymorphism could be used to construct generic heterogenous structures. It was also interesting because I just reviewed a paper on smart pointers, and the Cell class that we were writing was similar to one - a wrapper around a raw pointer whose memory management was being dealt with internally. It's interesting to write the copy-assignment and copy constructors for such classes, because you have to be very careful about strict ownership of the raw pointer(so that delete is called only once) and other possibilities of memory leaks.
The code for the FredkinCell and the ConwayCell in itself was not hard to do at all - especially since my partner and I spent a good amount of time thinking about the design (we did not give in to the temptation of writing monolithic un-maintainable monster functions to avoid unit tests - though it is kind of ambiguous whether you should write unit tests for every implementation of shared functions). Our initial design was the simple 1-pass update, in which every cell - dead or alive - was checking its neighbors and updating its state accordingly. However, when we ran the ConwayCell case using valgrind - this was taking nearly 8 minutes to run! There was an obvious need for re-design! So we re-designed it so that only the live cells would be checked and its neighbors' neighbor counts would be updated. Much faster, but wait! There's havoc on the generic Cell board. A FredkinCell won't update a ConwayCell which is diagonally its neighbor, and a ConwayCell will try to update the neighbor count of a diagonally placed FredkinCell whether it likes to or not! The realization was that we had to handle orthogonal neighbors (the kind that FredkinCells recognize) and diagonal neighbors differently. With that tweak, and some optimizations as far as vector handling was concerned, our run time using valgrind dropped to ~6s! And without the overhead of valgrind's checks, it runs in less than a second. Now that's what we're talking about!
Another interesting issue that I'm glad to have learned is the concept of the virtual destructor. If you get used to Java, you forget the dangers of polymorphism being turned OFF by default. One of these is the innocuous fact that the free destructors the compiler generates for you in C++ can be dangerous when it comes to inheritance hierarchies. If you assign a derived class object to a base pointer and then call delete on it, unless the base destructor has been marked virtual - this will cause undefined behavior (the base destructor will be invoked, not the derived one)! You have been warned!
Tuesday, April 17, 2012
Paper review
Smart Pointers - What, Why, Which? - Yonat Sharon
Smart pointers emulate raw pointers in C++, but handle memory management. This allows the programmer to use the pointer normally, but not worry about cleaning up behind it. The simplest example of smart pointers is auto_ptr which is simply a wrapper around a raw pointer, behaving identically as far as dereferencing and indirection is concerned, but the difference lies in the destructor which performs the delete on the contained raw pointer. As of the C++11 standard, std::auto_ptr has been deprecated in favor of std::unique_ptr. I will briefly discuss this later.
The article mentions many reasons for using smart pointers apart from the automatic cleanup mentioned above : smart pointers get initialized to NULL by default, the danger of dangling pointers is reduced since smart pointers allow you to explicitly test for NULL to ensure that the pointer is in fact pointing to something, enforcing strict ownership means that the same resource will not be deleted more than once, they allow for exception safety (see the RAII idiom) and preclude a situation which could cause a memory leak because an exception was thrown before delete could be called on the pointer. Smart pointers can also be used to increase efficiency by following the Copy on Write (COW) strategy - i.e. the same object is used by multiple pointers so long as they behave in a read-only fashion; the moment one of them attempts to modify the object, the smart pointer makes a copy. This kind of semantics is seen in the std::string class. Another reason for using smart pointers is their use in STL containers. Since STL containers store objects by value, it’s not possible to have Child objects being stored in a vector<Parent>. To overcome this, we could have a vector<Parent*>, however cleaning up afterwards is error-prone and not exception-safe. Using a smart pointer that cleans up after itself is obviously desirable. However, not all smart pointers can be used since STL containers may copy/ delete their elements while resizing, etc.Thus any smart pointer that transfers ownership (auto_ptr, may it rest in peace, did this) cannot be used.
So which smart pointers should one use, and when? The article keeps this section generalized, so I’ll try to review it in terms of the C++11 standard. If you have been using std::auto_ptr, then your best bet is to switch to std::unique_ptr keeping in mind that now ownership transfer is explicit using move and copy construction is prohibited. The following is taken from Danny Kalev’s C++ reference guide:
auto_ptr<int> ap1(new int);
auto_ptr<int> ap2 = ap1; // OK, albeit unsafe
unique_ptr<int> up1(new int);
unique_ptr<int> up2 = up1; // compilation error
unique_ptr<int> up1(new int);
unique_ptr<int> up2 = move(up1); // ok, explicit move
Thus unique_ptr should be used when we want a single pointer to an object whose memory will automatically get reclaimed when the single owning pointer gets destroyed.
On the other hand, shared_ptr allows multiple pointers to point to the same object and the object is reclaimed only when the last shared_ptr to it is destroyed (it keeps track of this using reference counting, so one must beware of cycles). The use of shared_ptr is recommended within STL containers. There are other smart pointers that ship with the Boost library, and the interested reader can find a brief summary of them here and an interesting discussion on them here. In conclusion, smart pointers can be quite helpful in preventing subtle memory management bugs, and with the introduction of some of the popular Boost smart pointers in the current C++ standard, there’s all the more reason to use them.
Saturday, April 14, 2012
#12 April 15th
The final project has been announced - it's the famous Conway's game of Life. It would be interesting to have glider guns as one of the input cases. It's also interesting to have another evolutionary-style project handed to us after Darwin. This one again, is solidly suited for object-oriented design and I suspect the first half of the semester was prepping us to get comfortable with the eccentricities of C++ so that we could tackle these with a lot of tools under our belts. This one also makes use of abstract classes and inheritance to reuse code, something which I have wanted to hone - and even if it's just one project, I appreciate that this is being thrown at us as part of the problem. I'm really looking forward to solving this one, but I need to get hold of a partner first! So, more on "Life experiences" later.
As for the material covered this week - we went through copy assignment in Java and C++ using a Complex class example. We also started on inheritance. Some of the interesting things that came out of this week's lectures are:
As for the material covered this week - we went through copy assignment in Java and C++ using a Complex class example. We also started on inheritance. Some of the interesting things that came out of this week's lectures are:
- In a constructor's initialization list, the order of variables is immaterial. Initialization of variables occurs in the order that the variables have been declared in the class.
- You can access private members of an object if it is the same type as yourself.
- A non-static const method CAN modify static data
delete 0is well defined to do nothing- The Rule of Three comes in to the picture when you're writing a class that has to perform explicit memory management (say you're writing the vector class and need to manage the underlying array). It states that: "If you need to explicitly declare either the destructor, copy constructor or copy assignment operator yourself, you probably need to explicitly declare all three of them." Something important to keep in mind! See this StackOverflow article for the full story on copying objects.
Sunday, April 8, 2012
#11 April 8th
I feel like a marathon runner entering the final few laps. There's 3 more weeks to go - so it's time to buckle up and get this thing done well.
This week had a diverse range of topics. We covered static and instance methods in both Java and C++. We also touched upon friends in C++. One interesting thing in that discussion was the following:
Say you have a templated global function:
Then, if you want to declare all instances of it as a friend in one of your classes, the syntax is:
However if you have another templated class C which contains f(), and you want all instances of C's f() to be friends with your class, then the syntax changes to:
Another new thing that I learned was the
However, as you can imagine, this is a slippery slope towards a landmine of bugs (interesting visualization, eh). If that constructor was now declared as explicit, then the line would not compile anymore.
One final bit of trivia to end this post - one can overload member functions (class methods) not simply by changing the number and/or type of arguments, but also by changing the constness of that method.
This week had a diverse range of topics. We covered static and instance methods in both Java and C++. We also touched upon friends in C++. One interesting thing in that discussion was the following:
Say you have a templated global function:
template <typename T>
int f();
Then, if you want to declare all instances of it as a friend in one of your classes, the syntax is:
template<typename U>
friend int f(); //Notice the U is not usedHowever if you have another templated class C which contains f(), and you want all instances of C's f() to be friends with your class, then the syntax changes to:
template<typename U>
friend int C<U>::f();Another new thing that I learned was the
explicit keyword. Since C++ allows implicit conversions of the parameters while constructing objects, if the class Foo had a constructor with a single int argument, ordinarily it would allow something like:
Foo f = 2;
However, as you can imagine, this is a slippery slope towards a landmine of bugs (interesting visualization, eh). If that constructor was now declared as explicit, then the line would not compile anymore.
One final bit of trivia to end this post - one can overload member functions (class methods) not simply by changing the number and/or type of arguments, but also by changing the constness of that method.
Sunday, April 1, 2012
#10 April 1st
It seems like I should write something witty given that it's April Fool's day, but I'm just too tired. This week has definitely been a learning experience. I was not aware of the tremendous variety with which variables could be declared and/ or initialized in C++. I know it sounds like a trivial thing, but it's not.
A case in point: We are working on the Darwin problem, which you can find more about here. This is, refreshingly, a problem which begs the OOP paradigm. So we made a Creature class to represent the different creatures in the world. Now the thing is, every Creature needs to have a reference to the Darwin world it belongs to. This is so that it can ask its world - "hey is there a wall ahead of me?" and other such useful questions. All of them need to have the same reference - so it's obvious that an externally created Darwin object must be passed to each Creature and stored as a const reference. However, in C++ you cannot initialize reference variables in the body of your constructor! And you definitely need to have it initialized since it's a reference variable, for crying out loud! Initialization lists come to the rescue. The initialization lists run before the constructors are called and can therefore initialize any member reference variables you may have declared.
That's not where the idiosyncrasies end. Have a static member variable in your class, do ya?! Well you can't initialize it in there. You MUST redeclare and intialize it outside the class in the form:
Type ClassName::staticMember = value;
Rather annoying!
An interesting list of other facts I learnt is:
A case in point: We are working on the Darwin problem, which you can find more about here. This is, refreshingly, a problem which begs the OOP paradigm. So we made a Creature class to represent the different creatures in the world. Now the thing is, every Creature needs to have a reference to the Darwin world it belongs to. This is so that it can ask its world - "hey is there a wall ahead of me?" and other such useful questions. All of them need to have the same reference - so it's obvious that an externally created Darwin object must be passed to each Creature and stored as a const reference. However, in C++ you cannot initialize reference variables in the body of your constructor! And you definitely need to have it initialized since it's a reference variable, for crying out loud! Initialization lists come to the rescue. The initialization lists run before the constructors are called and can therefore initialize any member reference variables you may have declared.
That's not where the idiosyncrasies end. Have a static member variable in your class, do ya?! Well you can't initialize it in there. You MUST redeclare and intialize it outside the class in the form:
Type ClassName::staticMember = value;
Rather annoying!
An interesting list of other facts I learnt is:
- Local static variables get initialized only once and consequent initializations don't throw an error, they are simply ignored. Thus they can be considered as global variables as far as value is concerned, but with limited scope.
- If you have a statement globally, such as
int j = g(), theng()runs before main, so that j can be initialized. - You cannot have the same name for globals in 2 different C++ files unless one of them is static, which will make the global variable file-scoped.
- Uninitialized built-in local variables get initialized to garbage, global ones to 0.
- A static class variable in Java has just one copy across template flavors. Thus one cannot declare a static variable of the template type when defining the templated class. This, however is allowed in C++, since there is a copy per template flavor.
Well, this has definitely been an eye-opening experience for me. I hope I don't garbage-collect it all!
Subscribe to:
Posts (Atom)