I like reading. Reading is good.

The reading of words, that is, not Reading the town. Not that Reading isn't a nice place, as far as I know, I've never visited it. Anyway;
    In my continuing mission to assimilate all things coding I've decided that the internet just isn't enough. It's not bad, hell if it didn't exist I'd have probably died trapped in a chimney, but it'll probably never be as good as shelling out for some professional advice. To that end I have invested in copies of Scott Meyers' Effective C++, Effective STL and More Effective C++ - and I'm glad I did. Although these books are a little old now it really struck me how amateurish my practices often are. I guess this is, in part, not just down to how the books explain how certain techniques should be applied, but why and in which situation - explanations often omitted when trawling forums on the internet, usually because I'm looking to deal with a specific problem. I also found it interesting how a book written in 1995 implores the use of smart pointers (yes, I'm still going on about them) even though they never actually made it to the official standard until 2011. The books are split into 'items' which cover a wide variety of topics, idioms and programming practices. If I'm honest I still don't fully understand all of them, so I'm sure I'll often be revisiting the books over the coming years, but on the other hand some of the items pointed out things to me which I really ought to have deduced on my own. In fact I would probably feel a little stupid if it weren't for the fact that some of these things don't actually seem to be widely acknowledged on the internet which, up until now, had been my sole source of code practice income. One such point was this, consider:

std::vector<int> myVec;
for(int i = 0; i < 1000; i++) myVec.push_back(i);

We all know the STL containers are magic. They handle their own memory allocation and make our lives easier (within reason, for once I shall skip the raw pointers topic here) which is why we love them. What I and, I suspect, many other budding programmers fail to do is consider what is actually going on behind the scenes because, after all, they aren't *really* magic. A vector like this will allocate an arbitrary amount of memory, let's say for the sake of argument enough space to hold 20 ints. Once it runs out of space it reallocates and resizes itself taking these approximate steps:

allocate new memory - normally twice the currently used amount
create a new vector in that memory
copy the old vector to the new vector via a call to the copy constructor of every element
destroy the objects in the old vector and the old vector itself (again, one destructor call for every object the vector holds - which can be many - plus the vector's destructor)
deallocate the old memory and return it to the heap

That's quite a few operations, repeated 6 times in the given example (although that may be a bit extreme). All of these can be negated simply by making sure that there is enough space in the first place, either with

myVec.reserve(1000);

 or even

std::vector<int> myVec(1000);

It's a small thing, but it all adds up over the course of an entire program.

    On the other hand, however, there are items which highlight points that are spread all over the internet, yet somehow I managed to miss them (probably because I'm still prone to making assumptions, despite knowing better). One such item pointed out that if you are creating a class which will be inherited from you should always make the destructor virtual. I have since seen this posted in various forums, often as a mistake made by other programmers so I don't feel quite so bad. What made it so facepalmingly obvious was the explanation given by Meyers, which is, again, often omitted or poorly put over in forum posts.

Assume you have 3 classes, a base class and 2 derived classes:

class Base
{
public:
    Base();
    ~Base();
    void myFunc();
};

class DerivedOne : public Base
{
public:
    DerivedOne();
    ~DerivedOne();
};

class DerivedTwo : public Base
{
public:
    DerivedTwo();
    ~DerivedTwo();
};

Using the classes as they are will work as expected when you do this:

DerivedOne d1;
//do stuff
//d1 goes out of scope and destructor is called

because your compiler knows d1 is of type DerivedOne and that it should call DerivedOne's destructor when it's time to. However it is not uncommon to take advantage of polymorphism by creating a vector of (smart) pointers of type Base and using the heap to allocate objects from derived classes to access their common interface:

std::vector<Base*> objVect; //we know to use smart pointers really ;)
objVect.push_back(new DerivedOne);
objVect.push_back(new DerivedTwo);
for(auto i = objVect.begin(); i != objVect.end() ++i)
    i->myFunc();

This is perfectly legitimate, but a problem arises when it's time to destroy the objects (be it manually or via a smart pointer). Because the objects were created via a pointer to Base rather than the type of object which is actually being pointed to, the compiler, having seen that the destructor for Base is not virtual, will therefore call the destructor for Base without first having called the destructor specific to the object type. This means that any tidying up required by an object's destructor is not performed, resulting in a partially destructed object; a resource leak and, well, a bit of a mess. Simply declaring the destructor of Base as virtual will make sure any derived classes will override it, so that the correct one is called when necessary. It's a small thing, but with large ramifications.

    Needless to say that, in the past couple of weeks, my library of personal code has been subject to some rather close scrutiny as I try to undo all my maleducated mistakes. Meyers' books are full of many more relevant and interesting items, but I'll quit here adding only this: if you haven't read these books (or any other highly recommended publications) it's probably time to invest. I can guarantee it will most likely be worth it.

Oh and due to my scrutiny I've also updated sfchat - it seems I managed to miss a rather obvious memory leak using a std::list of raw pointers. It's fixed now though, smart pointers to the rescue! The update is available by checking out the latest source. ^_^

Comments

Popular Posts