Monday 17 June 2024

Learn C++ by Example: Chapter 6

I have been sharing some details about my latest book "Learn C++ by Example", and gave an overview of the chapter 5 last time.



You can buy my book directly here: http://mng.bz/AdAQ - or just go look at the table of contents. You can also buy it from Amazon: https://amzn.to/4dMJ0aG 

Chapter 6 looks at smart pointers and dynamic polymorphism. We create a "Blob" class which moves forwards or possibly backwards. We end up with different Blob type, giving different possible movements, so can race the blobs.

The chapter starts with an abstract class, so we can write various derived classes for the race. The following might seem like a good starting point, but has several problems:

class Blob 

public:

    virtual void step() = 0;

    virtual int total_steps() const = 0; 

};

Did you spot any? 

Do we want to copy this class? Do we want a default constructor? It's worth being explicit about such things:

Blob() = default;

Blob(Blob const&) = delete;

Blob& operator=(Blob const&) = delete;

More importantly, a base class needs a virtual destructor.

virtual ~Blob() = default;

This has been the case in C++ since we've had classes, so probably isn't a surprise. Being able to say =default rather than {} might be though. C++11 introduced to ability to mark special member functions (and more besides) as defaulted or deleted.

We then look at the special member functions a class can have:

  • default constructor, X()
  • copy constructor, X(const X&)
  • copy assignment, operator = (const X&)
  • move constructor, X(X&&)
  • move assignment, operator = (X&&)
  • destructor, ~X()
Implementing one can block the others, and the table in Howard Hinnant's short blog is very useful if you can't recall what affects what.



The concrete classes move in various ways, for example a simple blob might move a fixed number of steps each time we call the step function. The book doesn't show how to use graphics libraries, so the reader can concentrate on learning newer C++ features, but if we use something like the SFML, we could display the marching blobs:


The book demonstrates how to use the console, with *s to represent a blob moving.

The book shows how to create other blobs using random distributions. This adds a bit more excitement, since you can't be certain which might win.

If we make a vector of blobs we need to use smart pointers, so that we can get the polymorphism we need. We can't make a std::vector<Blog> because blob is abstract. Even if it were not, we would slice derived classes, which is a bad thing.

In order to achieve polymorphism, we use a std::vector<std::unique_ptr<Blob>>. To add blobs, for example a StepperBlob which just marches at a set pace, we emplace a unique_ptr as follows:
blobs.emplace_back(std::make_unique<StepperBlob>());
We can add any other derived types too. 

Smart pointers are so much better than trying to handle raw pointers directly. The vector will call the destructor of each element when it goes out of scope, tidying up for us since a std::unique_ptr deletes the underlying raw pointer for us automatically. 

Some questions for you
1. Do you know which special member functions remain when you add a virtual destructor? (Cheat and look at Howard's table if you're not sure)
2. Can you list all the smart pointers in C++?
3. Which C++ distributions have you used? (If you would like some extra details, let me know and I'll write a short blog about these too).

No comments:

Post a Comment