Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Learning C++ Functional Programming

You're reading from   Learning C++ Functional Programming Explore functional C++ with concepts like currying, metaprogramming and more

Arrow left icon
Product type Paperback
Published in Aug 2017
Publisher
ISBN-13 9781787281974
Length 304 pages
Edition 1st Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Wisnu Anggoro Wisnu Anggoro
Author Profile Icon Wisnu Anggoro
Wisnu Anggoro
Arrow right icon
View More author details
Toc

Table of Contents (9) Chapters Close

Preface 1. Diving into Modern C++ 2. Manipulating Functions in Functional Programming FREE CHAPTER 3. Applying Immutable State to the Function 4. Repeating Method Invocation Using Recursive Algorithm 5. Procrastinating the Execution Process Using Lazy Evaluation 6. Optimizing Code with Metaprogramming 7. Running Parallel Execution Using Concurrency 8. Creating and Debugging Application in Functional Approach

Avoiding manual memory management with smart pointers

The smart pointers are highly useful and have an essential knowledge in using C++ efficiently. C++11 added many new abilities for the smart pointer we can find in the memory header file. For a long time, before C++11, we used auto_ptr as a smart pointer. However, it was quite unsafe since it had incompatible copy semantics. It's also deprecated now, and we should not use it anymore. Fortunately, C++ has presented unique_ptr, which has a similar functionality, but with additional features, such as adding deleters and support for arrays. Anything we can do with auto_pt, we can and should do with unique_ptr instead. We will discuss unique_ptr in depth along with other new smart pointers in C++11--shared_ptr and weak_ptr.

Replacing a raw pointer using unique_ptr

The next pointer we will see is the unique_ptr pointer. It is fast, efficient, and a near drop-in replacement for raw or naked pointers. It provides exclusive ownership semantics, which exclusively owns the object that it points to. By its exclusiveness, it can destroy the object when its destructor is called if it has a non-null pointer. It also cannot be copied due to its exclusiveness. It has no copy constructor and copy assignment. Although it cannot be copied, it can be moved since it provides a move constructor and a move assignment.

These are the methods we can use to construct unique_ptr:

    auto up1 = unique_ptr<int>{};
auto up2 = unique_ptr<int>{ nullptr };
auto up3 = unique_ptr<int>{ new int { 1234 } };

Based on the preceding code, up1 and up2 will construct two new unique_ptr that point to nothing (null), whereas up3 will point to the address that holds the 1234 value. However, C++14 adds a new library function to construct unique_ptr, that is, make_unique. So, we can construct a new unique_ptr pointer as follows:

    auto up4 = make_unique<int>(1234);

The up4 variable will also point to the address that holds the 1234 value.

Now, let's take a look at the following block of code:

    /* unique_ptr_1.cpp */
#include <memory>
#include <iostream>

using namespace std;

struct BodyMass
{
int Id;
float Weight;

BodyMass(int id, float weight) :
Id(id),
Weight(weight)
{
cout << "BodyMass is constructed!" << endl;
cout << "Id = " << Id << endl;
cout << "Weight = " << Weight << endl;
}

~BodyMass()
{
cout << "BodyMass is destructed!" << endl;
}
};

auto main() -> int
{
cout << "[unique_ptr_1.cpp]" << endl;
auto myWeight = make_unique<BodyMass>(1, 165.3f);
cout << endl << "Doing something!!!" << endl << endl;
return 0;
}

We try to construct a new unique_ptr pointer that points to the address that holds a BodyMass data type. In BodyMass, we have a constructor as well as a destructor. Now, let's see how the unique_ptr pointer works by running the preceding code. The output we get on the screen should be like the following screenshot:

As we can see in the preceding screenshot, the constructor is invoked when we construct unique_ptr. Moreover, unlike the traditional C++ language, where we have to free the memory up when we use a pointer, in modern C++, the memory will be freed up automatically when it is out of scope. We can see that the destructor of BodyMass is invoked when the program exits, which means myWeight is out of scope.

Now, let's test the exclusiveness of unique_ptr by analyzing the following code snippet:

    /* unique_ptr_2.cpp */
#include <memory>
#include <iostream>

using namespace std;

struct BodyMass
{
int Id;
float Weight;

BodyMass(int id, float weight) :
Id(id),
Weight(weight)
{
cout << "BodyMass is constructed!" << endl;
cout << "Id = " << Id << endl;
cout << "Weight = " << Weight << endl;
}

BodyMass(const BodyMass &other) :
Id(other.Id),
Weight(other.Weight)
{
cout << "BodyMass is copy constructed!" << endl;
cout << "Id = " << Id << endl;
cout << "Weight = " << Weight << endl;
}

~BodyMass()
{
cout << "BodyMass is destructed!" << endl;
}
};

auto main() -> int
{
cout << "[unique_ptr_2.cpp]" << endl;

auto myWeight = make_unique<BodyMass>(1, 165.3f);

// The compiler will forbid to create another pointer
// that points to the same allocated memory/object
// since it's unique pointer
//auto myWeight2 = myWeight;

// However, we can do the following expression
// since it actually copies the object that has been allocated
// (not the unique_pointer)
auto copyWeight = *myWeight;

return 0;
}

As we can see in the preceding code, we see that we can't assign the unique_ptr instance to another pointer since it will break the exclusiveness of unique_ptr. The compiler will throw an error if we make the following expression:

    auto myWeight2 = myWeight;

However, we can assign the value of the unique_ptr to another object since it has been allocated. To prove it, we have added a copy constructor to log when the following expression is executed:

    auto copyWeight = *myWeight;

If we run the preceding unique_ptr_2.cpp code, we will see the following output on the screen:

As we can see in the preceding screenshot, the copy constructor is called when the copy assignment is executed. It proves that we can copy the value of the unique_ptr object but not the object itself.

As we discussed earlier, unique_ptr has moved the constructor, although it has no copy constructor. The use of this construction can be found in the following piece of code:

    /* unique_ptr_3.cpp */
#include <memory>
#include <iostream>

using namespace std;

struct BodyMass
{
int Id;
float Weight;

BodyMass(int id, float weight) :
Id(id),
Weight(weight)
{
cout << "BodyMass is constructed!" << endl;
cout << "Id = " << Id << endl;
cout << "Weight = " << Weight << endl;
}

~BodyMass()
{
cout << "BodyMass is destructed!" << endl;
}
};

unique_ptr<BodyMass> GetBodyMass()
{
return make_unique<BodyMass>(1, 165.3f);
}

unique_ptr<BodyMass> UpdateBodyMass(
unique_ptr<BodyMass> bodyMass)
{
bodyMass->Weight += 1.0f;
return bodyMass;
}

auto main() -> int
{
cout << "[unique_ptr_3.cpp]" << endl;

auto myWeight = GetBodyMass();

cout << "Current weight = " << myWeight->Weight << endl;

myWeight = UpdateBodyMass(move(myWeight));

cout << "Updated weight = " << myWeight->Weight << endl;

return 0;
}

In the preceding code, we have two new functions--GetBodyMass() and UpdateBodyMass(). We construct a new unique_ptr object from the GetBodyMass() function, then we update the value of its Weight using the UpdateBodyMass() function. We can see that we use the move function when we pass an argument to the UpdateBodyMass() function. It's because unique_ptr has no copy constructor, and it has to be moved in order to update the value of its property. The screen output of the preceding code is as follows:

Sharing objects using shared_ptr

In contrast to unique_ptr, shared_ptr implements shared ownership semantics, so it offers the ability of copy constructor and copy assignment. Although they have a difference in the implementation, shared_ptr is actually the counted version of unique_ptr. We can call the use_count() method to find out the counter value of the shared_ptr reference. Each instance of the shared_ptr valid object is counted as one. We can copy the shared_ptr instance to other shared_ptr variables and the reference count will be incremented. When a shared_ptr object is destroyed, the destructor decrements the reference count. The object will be deleted only if the count reaches zero. Now let's examine the following shared_ptr code:

    /* shared_ptr_1.cpp */
#include <memory>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[shared_ptr_1.cpp]" << endl;

auto sp1 = shared_ptr<int>{};

if(sp1)
cout << "sp1 is initialized" << endl;
else
cout << "sp1 is not initialized" << endl;
cout << "sp1 pointing counter = " << sp1.use_count() << endl;
if(sp1.unique())
cout << "sp1 is unique" << endl;
else
cout << "sp1 is not unique" << endl;
cout << endl;

sp1 = make_shared<int>(1234);

if(sp1)
cout << "sp1 is initialized" << endl;
else
cout << "sp1 is not initialized" << endl;
cout << "sp1 pointing counter = " << sp1.use_count() << endl;
if(sp1.unique())
cout << "sp1 is unique" << endl;
else
cout << "sp1 is not unique" << endl;
cout << endl;

auto sp2 = sp1;

cout << "sp1 pointing counter = " << sp1.use_count() << endl;
if(sp1.unique())
cout << "sp1 is unique" << endl;
else
cout << "sp1 is not unique" << endl;
cout << endl;

cout << "sp2 pointing counter = " << sp2.use_count() << endl;
if(sp2.unique())
cout << "sp2 is unique" << endl;
else
cout << "sp2 is not unique" << endl;
cout << endl;

sp2.reset();

cout << "sp1 pointing counter = " << sp1.use_count() << endl;
if(sp1.unique())
cout << "sp1 is unique" << endl;
else
cout << "sp1 is not unique" << endl;
cout << endl;

return 0;
}

Before we examine each line of the preceding code, let's take a look at the following output that should appear on the console window:

First, we create a shared_ptr object named sp1 without instantiating it. From the console, we see that sp1 is not initialized and the counter is still 0. It is also not unique since the pointer is pointed to nothing. We then construct sp1 using the make_shared method. Now, sp1 is initialized and the counter becomes 1. It also becomes unique since it's only one of the shared_ptr object (proven by the value of the counter that is 1). Next, we create another variable named sp2, and copy sp1 to it. As a result, sp1 and sp2 now share the same object proven by the counter and the uniqueness value. Then, invoking the reset() method in sp2 will destroy the object of sp2. Now, the counter of sp1 becomes 1, and it is unique again.

In the shared_ptr_1.cpp code, we declare the unique_ptr object using shared_ptr<int>, then invoke make_shared<int> to instance the pointer. It's because we just need to analyze the shared_ptr behavior. However, we should use make_shared<> for shared pointers since it has to keep the reference counter somewhere in memory and allocates the counter and memory for objects together instead of two separate allocations.

Tracking the objects using a weak_ptr pointer

We have discussed the shared_ptr in the preceding section. The pointer is actually a little bit fat pointer. It logically points to two objects, the object being managed and the pointing counter using the use_count() method. Every shared_ptr basically has a strong reference count that prevents the object from being deleted and a weak reference count that does not prevent the object being deleted if the shared_ptr object's use count reaches 0, although we don't even use the weak reference count. For this reason, we can use only one reference count so we can use the weak_ptr pointer. The weak_ptr pointer refers to an object that is managed by shared_ptr. The advantage of weak_ptr is that it can be used to refer to an object, but we can only access it if the object still exists and without preventing the object from being deleted by some other reference holder if the strong reference count reaches zero. It is useful when we deal with data structures. Let's take a look at the following block of code to analyze the use of weak_ptr:

    /* weak_ptr_1.cpp */
#include <memory>
#include <iostream>

using namespace std;

auto main() -> int
{
cout << "[weak_ptr_1.cpp]" << endl;

auto sp = make_shared<int>(1234);

auto wp = weak_ptr<int>{ sp };

if(wp.expired())
cout << "wp is expired" << endl;
else
cout << "wp is not expired" << endl;
cout << "wp pointing counter = " << wp.use_count() << endl;
if(auto locked = wp.lock())
cout << "wp is locked. Value = " << *locked << endl;
else
{
cout << "wp is unlocked" << endl;
wp.reset();
}
cout << endl;

sp = nullptr;

if(wp.expired())
cout << "wp is expired" << endl;
else
cout << "wp is not expired" << endl;
cout << "wp pointing counter = " << wp.use_count() << endl;
if(auto locked = wp.lock())
cout << "wp is locked. Value = " << *locked << endl;
else
{
cout << "wp is unlocked" << endl;
wp.reset();
}
cout << endl;

return 0;
}

Before we analyze the preceding code, let's take a look at the following screenshot from the output console if we run the code:

At first, we instantiate shared_ptr and, as we discussed previously, the weak_ptr points to the object managed by shared_ptr. We then assign wp to the shared_ptr variable, sp. After we have a weak_ptr pointer, we then check its behavior. By calling the expired() method, we can figure out whether the referenced object was already deleted. And, since the wp variable is just constructed, it is not expired yet. The weak_ptr pointer also holds the value of the object counting by calling the use_count() method, as we used in shared_ptr. We then invoke the locked() method to create a shared_ptr that manages the referenced object and finds the value weak_ptr is pointing at. We now have a shared_ptr variable pointing to the address that holds the 1234 value.

We reset sp to nullptr afterward. Although we don't touch the weak_ptr pointer, it is also changed. As we can see from the console screenshot, now wp is expired since the object has been deleted. The counter also changes and becomes 0 since it points to nothing. Moreover, it is unlocked since the shared_ptr object has been deleted.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at AU $24.99/month. Cancel anytime