C++ Tour Part III

Shared Pointer

shared pointer is a smart pointer that allows for distributed ownership of data. Each time it is assigned, a reference count is incremented, indicating one more owner of the data. When the pointer goes out of scope or an owner calls reset, the reference count is decremented. When the reference count goes to 0, the data pointed to is deallocated. Use make_shared:
auto person = std::make_shared<Person>();
if(person)
{
    cout << "Person's address is " << person->address;
}
C++17 allows use of shared pointers to point to dynamically allocated arrays. However, make_shared cannot be used. Below is an example:
shared_ptr<Person[]> persons(new Person[10]);
persons[0]._name = "Jack Sparrow";
persons[0]._address = "Caribbean";
cout << "Address of first person "<<persons[0].address << endl;
A better solution is use std::vector as it can dynamically grow and shrink.
std::vector<std::shared_ptr<Person>> persons;
auto person1 = std::make_shared<Person>("Jack Sparrow","Caribbean");
auto person2 = std::make_shared<Person>("Hector Barbossa","Bahamas");
persons.push_back(person1);
persons.push_back(person2);

for (auto& person : persons)
{
    std::cout << "Person name is " << person->_name << " and address is " << person->_address << "\n";
}

Const

The first use of const is in declaring and defining values that are initialized once and cannot change. These replace the #define macro constants from C. Examples:
const double PI = 3.14;
const int NUMVALUES = 42;
const std::string quote = "To be or not to be";

const in parameters

The second place const can be used is in making sure parameters cannot be modified. For example, if a function takes an int* pointer, passing the pointer with const ensures the function cannot change the value. Example:
int main()
{
    int num = 5;
    int *val = &num;
    foo(val);
}

void foo(const int* value)
{
    *value = 6; //will fail to  compile
}

References

A reference is an alias for an existing variable. Example:
double aDouble = 3.14
double& refToDouble = aDouble;
The special thing about references is that one still uses them as one would a normal variable but under the hood they are "pointers" to the original variable. Therefore, modifying the reference is the same as modifying the original variable. Example continued from before:
refToDouble = 6.0;
cout << "Double value is " << aDouble << endl; //prints out 6.0

Pass by Reference

When parameters are passed to functions a copy of the variable is made and that copy is modified within the function. To modify the original variable, the C idiom is to pass a pointer to the variable. Example:
int main()
{
    int x = 5;
    int y = 3;
    swap(&x, &y);
    printf("x is %d and y is %d",x,y); //prints out 3 and 5
}
void swap(int *a , int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}
C++ avoids using pointers by using pass by reference. The same swap function is implemented as follows in C++:
int main()
{
    int a = 2;
    int b = 3;
    swap(a,b);
}
void swap(int &a , int &b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}
Note that the syntax allows for the references to be used as normal variables, but the original variables are modified.
When returning a large structure or class object, the traditional idiom was to pass in a non-const reference to the structure or object, and let the function modify it. C++11 move semantics remove that need.
Pass by const Reference
When you need to efficiently pass in a value to a function but ensure that it is not modified, use pass by const reference. Example:
void foo(const int& a)
{ 
    int b = a * 2;
    a++; //compiler will throw an error here as one attempts to modify a.
}
When passing a std::string by const reference, passing in string literals works.

Type Inference

Type inference is the mechanism with which the compiler deduces the type of an expression. Two keywords, auto and decltype are used in C++ for type inference. auto is used as follows:
auto x = 2 + 3;
The advantage of using auto becomes apparent when dealing with complex types returned by a function. Note that using auto strips away reference and const reference qualifiers and makes copies, so if a function is returning a reference, be sure to use auto&. Similarly use const auto& for const reference.
Example:
int& bar()
{ 
    int* dynInt = new int;
    *dynInt = 5;
    return *dynInt;
}

auto retVal = bar(); //A copy is made here. Use auto& instead.
decltype takes an expression as an argument and infers the type of that expression. The advantage is that reference and const references are not stripped out. Example:
decltype(bar()) retVal = bar(); 

Classes

A class models real world and abstract objects and can be viewed as a blueprint for making objects. Classes are declared in a header (.h) file and their definitions are in a .cpp file. Here is an example of a class:
checkingaccount.h:
#ifndef BANKACCOUNT_H
#define BANKACCOUNT_H
#include <string>
using std::string;
class CheckingAccount 
{ 
    public:
        CheckingAccount(string accountHolder, string accountNumber, double balance);
        double getBalance() const;
        string getAccountHolder() const;
        string getAccountNumber() const;
        void deposit(double amount);
        string withdraw(double amount);
        ~CheckingAccount();

    private:
        string mAccountHolder;
        string mAccountNumber;
        double mBalance;
}
#endif
checkingaccount.cpp:
#include "checkingaccount.h"

CheckingAccount::CheckingAccount(string accountHolder, string accountNumber, double balance)
{ 
    mAccountHolder = accountHolder;
    mAccountNumber = accountNumber;
    mBalance = balance;
}

double CheckingAccount::getBalance() const
{
    return mBalance;
}

string CheckingAccount::getAccountHolder() const
{
    return mAccountHolder;
}

string CheckingAccount::getAccountNumber() const
{
    return mAccountNumber;
}

void CheckingAccount::deposit(double amount)
{
    mBalance += amount;
}

string CheckingAccount::withdraw(double amount)
{
    if(mBalance - amount <= 0.0)
    {
        return "Insufficient Funds";
    }
    mBalance -= amount;
    return "Amount withdrawn successfully";
}

CheckingAccount::~CheckingAccount()
{
    //no cleanup for this case
}
The constructor is a method with the same name as the class with no return type. It is called when an object is initialized on the stack or the heap. The destructor is called when the object on the stack goes out of scope or when delete is called on a heap-allocated object. The constructor is responsible for initializing the class whereas the destructor is responsible for cleanup, include closing file handles, freeing up memory allocated by the object, and so on. It is the function marked with the ~.
constructor initializer is the preferred method of initialization. Its syntax is as follows:
CheckingAccount::CheckingAccount(string accountHolder, long accountNumber, double balance)
    :mAccountHolder(accountHolder),
     mAccountNumber(accountNumber),
     mBalance(balance)
{ 
}

Using classes

Example of using classes is as follows:
#include "checkingaccount.h"
#include <memory>
using std::unique_ptr;
using std::cout;
using std::endl;

int main()
{
    CheckingAccount chkAcnt("Jack Sparrow","61723",10000); //allocated on the stack.
    chkAcnt.deposit(100.0);
    chkAcnt.withdraw(50.0);
    auto chkAccntTwo = make_unique<CheckingAccount>("Hector Barbossa",61724,10000);
    chkAccntTwo->deposit(1000.00);
    chkAccntTwo->withdraw(5000.00);
    cout << chkAccountTwo.getAccountHolder() <<"'s balance is "<< chkAccntTwo->getBalance() << endl;
} //chkAcnt destructor is called here. chkAccntTwo destructor also called here as unique_ptr goes out of scope.

References:

Gregoire, M. (2018). Professional C++. Indiana, IN: John Wiley & Sons.

Comments

Popular posts from this blog

QTreeView and QTableView dynamic changes

C++ strings and string_view