Sizes of data types in c++

centimeters-close-up-depth-of-field-162500

This post explains sizes of different data types, structures and classes under various circumstances.

#include <iostream>
#include <atomic>
#include <string>
#include <vector>
#include <set>
#include <queue>
#include <forward_list>
#include <memory>
// An empty class without any data members
class EmptyClass{};

// class with member variable int
class X{
  int a;
};

// class with member variable int and double
class Y{
  int a;
  double b;
};

// class with static variable int
class StaticMember{
  static int a;
};

// packed structure
class __attribute__((packed)) YPacked{
int a;
double b;
};

// packed large structure
class __attribute__((packed)) LargeStruct {
int a;
double b;
float c;
char d;
double l;
double m;
int n;
int o;
double p;
double q;
double t;
double u;
double v;
double w;
char s;
double x;
float y;
int z;
};

// class with a single virtual function
class VFunClass{
  public:
    virtual void fun(){std::cout << "class with virtual function\n";};
};

// class with a virtual destructor.
class VDesttructorClass{
  public:
    virtual ~VDesttructorClass(){
      std::cout << "in the virtual destructor\n";
    }
};


// Multi level class hierarchy
class Base{};

class Derived: public Base{

};

class Derived1: public Base{
};

class Derived2: public Base{
};

class MostDerived: public Derived, public Derived1, public Derived2{};
class VBase{};

class VDerieved: public virtual VBase{

};

class VDerieved1: public virtual VBase{
};


class VMostDerieved: public VDerieved, public VDerieved1{

};

int main(){
  std::string s = "test string";
  std::cout << "sizeof(s) "<< sizeof(s) << '\n'; // 24
  std::cout << "s.size() " << s.size() << '\n'; // 11


  std::vector<int> v{1,2,3,4,5};
  std::cout << "sizeof(v) "<< sizeof(v) << '\n'; // 24
  std::cout << "v.size() " << v.size() << '\n'; // 5

  std::set<int> set{1,2,3,4,5};
  std::cout << "sizeof(set) "<< sizeof(set) << '\n'; // 24
  std::cout << "set.size() " << set.size() << '\n'; // 5

  std::queue<int> queue;
  for(int i = 0; i < 5; ++i) queue.push(i);
  std::cout << "sizeof(queue) "<< sizeof(queue) << '\n'; // 48
  std::cout << "queue.size() " << queue.size() << '\n'; // 5

  std::forward_list<int> fl;
  auto it = fl.before_begin();
  it = fl.emplace_after ( it, 100 );
  it = fl.emplace_after ( it, 200 );
  it = fl.emplace_after ( it, 300 );
  std::cout << "sizeof(fl) "<< sizeof(fl) << '\n'; // 8

// Size of X is 4 because there is a single data member in the class/struct
  std::cout << "sizeof(X) "<< sizeof(X) << '\n'; // 4

// Size of Y should be 12 i.e. addition of int (4) + double (8)= 12
// but its size if 16 because the compiler aligns the class/struct
// by multiples of its max member.
  std::cout << "sizeof(Y) "<< sizeof(Y) << '\n'; // 16

// Size of a packed structure = sum of sizes of member variables
  std::cout << "sizeof(YPacked) "<< sizeof(YPacked) << '\n'; // 12

  std::unique_ptr<int> uniq_ptr = std::make_unique<int>(10);
  std::cout << "sizeof(uniq_ptr) " << sizeof(uniq_ptr) << '\n'; // 8

  std::shared_ptr<int> sh_ptr = std::make_shared<int>(10);
  std::cout << "sizeof(sh_ptr) " << sizeof(sh_ptr) << '\n'; // 16

  std::weak_ptr<int> w_ptr = std::make_shared<int>(10);
  std::cout << "sizeof(w_ptr) " << sizeof(w_ptr) << '\n'; // 16

  std::cout << "sizeof(EmptyClass) "<< sizeof(EmptyClass) << '\n'; // 1 
  std::cout << "sizeof(VFunClass) " << sizeof(VFunClass) << '\n'; // 8
  std::cout << "sizeof(VDesttructorClass) " << sizeof(VDesttructorClass) << '\n'; // 8

// Size of most Derived class in the hierarchy with 3 NON virtual 
// Base classes has 2 * 1 bype per class Hence the size if 3
  std::cout << "sizeof(MostDerived) " << sizeof(MostDerived) << '\n'; // 3

// Size of most Derived class in the hierarchy with 2 virtual Base classes
// has 2 * 8 bype (v_ptr). Hence the size if 16.
std::cout << "sizeof(VMostDerived) " << sizeof(VMostDerived) << '\n'; // 16

// Size of class with only static variable is 1 i.e.
// it does not include size of static in the size of class.
  std::cout << "sizeof(StaticMember) " << sizeof(StaticMember) << '\n'; // 1

  std::atomic<int> i = 10;
  std::cout << "sizeof(std::atomic<int>) " << sizeof(i) << '\n'; // 4

//size of packed atomic structure is 16
  std::atomic<double> d;
  std::cout << "sizeof(std::atomic<double>) " << sizeof(std::atomic<double>) << '\n'; // 16

//size of packed atomic structure is 16
  std::atomic<YPacked> yp;
  std::cout << "sizeof(std::atomic<YPacked>) " << sizeof(std::atomic<YPacked>) << '\n'; // 16
}


Sublime Build System

{
"cmd": "g++ -std=c++17 \"${file}\" -o \"${file_path}/${file_base_name}\" && \"${file_path}/${file_base_name}\"",
   "file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
   "working_dir": "${file_path}",
   "selector": "source.c, source.c++",
   "shell": true,
   "variants":
   [
    
     {
       "name": "Run",
      "cmd": "g++ -std=c++17 \"${file}\" -o \"${file_path}/${file_base_name}\" && \"${file_path}/${file_base_name}\""
     }
   ]
  }

Vector back inserter example + algorithm

The problem is pretty simple: there is a list of time ranges for meetings and need to show the meetings as continuous block in calendar.

70940aec3ea953621db1b8faf619a5ca_2

As per modern c++ I have used std::back_inserter_iterator for vector instead of passing vector by value or by reference. This approach is much cleaner, modern and saves a lot of copying of data compared to old style.

// i/p: [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]
// o/p: [(0, 1), (3, 8), (9, 12)]

#include <iostream>
#include <vector>
#include <algorithm>

struct Meeting; // define meeting

using it = std::vector<Meeting>::iterator;
// use an back inserter iterator to pass as function argument instead
// of passing vector by reference or creating vector on heap
using bi = std::back_insert_iterator<std::vector<Meeting>>;

struct Meeting{
  int start_time, end_time;
  // default constructor
  Meeting() = default;
 // parameterized constructor
  Meeting(int start_time, int end_time):start_time{start_time},end_time{end_date}{}
  
  // override < operator used for sorting.
  bool operator <(const Meeting &other) const{
    return start_date < other.start_date;
  }

static void merge(it start, it finish, bi merged_meeting_bi){
  int start_time = start->start_time;
  int end_time = start->end_time;
  while(start < finish){
    start++;
    if(start->start_time > end_time){
      merged_meeting_bi = {start_time, end_time}; // create meeting using parameterized constructor
      start_time = start->start_time; 
      end_time = start->end_time;
    }else{
      end_time = std::max(start->end_time, end_time);
     }
   }
  }
};

int main(){
  std::vector<Meeting> meetings{{0, 1}, {3, 5}, {4, 8}, {10, 12}, {9, 10}};
  std::vector<Meeting> merged_meetings;
  auto bi = std::back_insert_iterator<std::vector<Meeting>>(merged_meetings);
  auto start = meetings.begin();
  auto finish = meetings.end();
  // sort by first time
  std::sort(start, finish);
  
for(const auto &meeting : meetings){
std::cout << meeting.start_time << ' ' << meeting.end_time << '\n';
}
Meeting::merge(start, finish, bi);

for(const auto & meeting: merged_meetings){
std::cout << meeting.start_time << ' ' << meeting.end_time << '\n';
}
return 0;
}

Dutch National Flag – Algorithm + TDD

640px-Flag_of_the_Netherlands.svg

The algorithm is pretty simple and achievable in O(n) time where n is the number of elements. Instead of using indexes, I am using 3 set of iterators and added bunch of test cases.

#include <gtest/gtest.h>
#include <algorithm> // random_shuffle
#include <vector>
#include <numeric> // accumulate

using it = std::vector<int>::iterator; // using typedef

void dnf(it less, it equal, it greater, int pivot){
  while(equal <= greater){
    if(*equal < pivot){
      std::iter_swap(less, equal);
      less++;
      equal++;
    }else if(*equal == pivot){
      equal++;
    }else{
      std::iter_swap(equal, greater);
      greater--;
    }
  }
}

TEST(RandomTest, dnfTest){
  std::vector<int> v{2, 2, 0, 1, 1, 0, 2, 1, 0, 1, 0, 1, 0, 2, 2};
  int pivot = 1;
  for(int i = 0; i < 1000; i++){
    dnf(v.begin(), v.begin(), v.end()-1, 1);
    std::string output = std::accumulate(std::next(v.begin()), v.end(), 
                               std::to_string(*v.begin()),
                               [](std::string s, int i){return s+= std::to_string(i);});

    EXPECT_EQ(output, "000001111122222");
  }
}

TEST(RandomTestTwoNumbers, dnfTest){
  std::vector<int> v{0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1};
  int pivot = 1;
  for(int i = 0; i < 1000; i++){
    dnf(v.begin(), v.begin(), v.end()-1, 1);
    std::string output = std::accumulate(std::next(v.begin()), v.end(), 
                               std::to_string(*v.begin()),
                               [](std::string s, int i){return s+= std::to_string(i);});

    EXPECT_EQ(output, "000000001111111");
  }
}

TEST(RandomTestTwoNumbersMidPivot, dnfTest){
  std::vector<int> v{0, 0, 0, 0,0,9,9,9,9,9,9,9,9};
  int pivot = 1;
  for(int i = 0; i < 1000; i++){
    dnf(v.begin(), v.begin(), v.end()-1, 1);
    std::string output = std::accumulate(std::next(v.begin()), v.end(), 
                               std::to_string(*v.begin()),
                               [](std::string s, int i){return s+= std::to_string(i);});

    EXPECT_EQ(output, "0000099999999");
  }
}

TEST(RandomTestTwoNumbersHighPivot, dnfTest){
  std::vector<int> v{0, 0, 0, 0,0,9,9,9,9,9,9,9,9};
  int pivot = 10;
  for(int i = 0; i < 1000; i++){
    dnf(v.begin(), v.begin(), v.end()-1, 1);
    std::string output = std::accumulate(std::next(v.begin()), v.end(), 
                               std::to_string(*v.begin()),
                               [](std::string s, int i){return s+= std::to_string(i);});

    EXPECT_EQ(output, "0000099999999");
  }
}

int main(int argc, char**argv, char**envArg)
{
    testing::InitGoogleTest(&argc, argv);
    return(RUN_ALL_TESTS());
}

Iterators – look and say sequence algorithm

Look and say sequence is fun little exercise.. here is the code

#include <iostream>
#include <vector>
#include <string>
#include <numeric>

std::string looknsay(int num){
  // initialize vector with vector of ints.
  // add default seed value to it [1].
  std::vector<std::vector<int>> look_n_say_seq{{1}};
  // iterate over until the number n is reached.
  for(int n = 0; n <= num; n++){
    auto latest_sequence = look_n_say_seq.back();
    std::vector<int> next_sequence;
   // keep a pair of iterators to move across the latest sequence from vector.
    auto i = latest_sequence.begin();
    auto j = latest_sequence.begin();
    while(i < latest_sequence.end()){
      for(;*i==*j && j < latest_sequence.end();++j){}
      // distance will give the count
      next_sequence.push_back(std::distance(i, j));
      // *i will give the actual number to iterate on
      next_sequence.push_back(*i);
      i = j;
    }
    // insert newly created sequence into the vector.
    look_n_say_seq.push_back(next_sequence);
  }
  auto latest_sequence = look_n_say_seq.back();
 // accumulate - join vector of ints to make string and return
  return std::accumulate(std::next(latest_sequence.begin()), 
                         latest_sequence.end(),
                          std::to_string(*latest_sequence.begin()),
                          [](std::string s, int i){return s += std::to_string(i);});
}
int main(){
  std::cout << looknsay(0) << '\n';
  std::cout << looknsay(1) << '\n'; // 11
  std::cout << looknsay(2) << '\n'; // 21
  std::cout << looknsay(3) << '\n'; // 1211
  std::cout << looknsay(4) << '\n'; // 111221
  std::cout << looknsay(5) << '\n'; // 312211
}

Implicit type conversion – tricky scenario

Here are some of the scenarios where the implicit  type conversion can be unintuitive  and tricky.

  • implicit conversion between int, float and bool
    #include <iostream>
    
    void print(char x){
      std::cout << "in the print method that has char arg\n";
    
    }
    
    void print(int x){
      std::cout << "in the print method that has int arg\n";
    }
    
    void print(bool x){
      std::cout << "in the print method that has bool arg\n";
    }
    
    int main(){
    int x =1;
    char ch = 'c';
    char * c = &ch;
    print(*c); // in the print method that has char arg
    print(*c+1); //in the print method that has int arg
    return 0;
    }

c++ OOP Grammar

The c++ language is a pure object oriented language. c++ has a lot of rules and extensive grammar. It’s very hard to write exhaustive list but I am including here what I understood.

Constructors:

  • c++compiler provides 5 types of constructors if user has not provide any.
  • c++ constructors has to be public in order to be able to make instances of the class.
  • Example:
    class Vehicle{};
    1. Default Constructor:
      • c++ compiler provides a default constructor without any parameters.
      • If any of other constructors are defined by user, compiler does not provide the default constructor.
      • if user provides any (parameterized or non-parameterized ) constructor,
        the compiler still provides other 4 constructors.
      • class Vehicle {
          public: 
            Vehicle(){};
        };
    2. Copy Constructor:
      • c++ compiler provides a copy constructor that does member wise copy.
      • the parameter must be reference otherwise there is a compiler error.
      • the parameter should be CONST but not necessary.
      • if a copy constructor is provided without default/parameterized constructor, then user must provide default/parameterized constructor.
      • If an object is not created and then assigned to another object, then copy assignment is called.
      • class Vehicle {
          public: 
            Vehicle(const Vehicle &v){};
            Vehicle(const Vehicle& obj){
            std::cout << "copy contructor called..\n";
          }
        };
    3. Copy assignment operator
      • c++ compiler provides a copy assignment operator that does member wise copy.
      • the parameter should be CONST but not necessary.
      • if a copy assignment operator is provided without default/parameterized constructor, then default/parameterized constructor is OPTIONAL.
      • If an object is created and then assigned to another object, then copy assignment is called.
      • class Vehicle {
        public: 
          Vehicle& operator=(constVehicle&& obj){
            std::cout << "move assignment operator called..\n";
            return *this;
          }
        };
    4.  Move constructor
      • c++ compiler provides a move constructor that moves the member variables.
      • If an object is not created and then moved to another object, then move operator is called.
      • if a move constructor is provided without default/parameterized constructor, then user must provide default/parameterized constructor .
    5. Move assignment Operator
      • c++ compiler provides a move assignment operator that moves the member variables.
      • If an object is created and then moved to another object, then move operator is called.
      • if a move assignment operator is provided without default/parameterized constructor, then default/parameterized constructor is OPTIONAL.

Following is code with all the examples:

class MyClass{
  public:
  MyClass() = default;

  MyClass(const MyClass& obj){
    std::cout << "copy constructor called..\n";
  }

  MyClass(MyClass && obj){
    std::cout << "move constructor called..\n"; 
  };

  MyClass& operator=(const MyClass& obj){
    std::cout << "copy assignment operator called..\n";
    return *this;
  }

  MyClass& operator=(const MyClass&& obj){
    std::cout << "move assignment operator called..\n";
    return *this;
  }
};

int main(){
  MyClass obj;
  MyClass obj2(obj); // copy assignment operator called..

  MyClass obj3;
  obj3 = obj; // copy assignment operator called..

  MyClass obj4;
  obj4 = std::move(obj); // move assignment operator called..

  MyClass obj5(std::move(obj2)); // move constructor called..

  std::cout << "test \n";
  return 0;
}

Converting Constructor

If a class provides parameterized constructor then it works as converting constructor. It can be used to instantiate the object but it is confusing. Example:

class X{
  int i;
  public:
    X(int i):i(i){}
    void print(){
      std::cout << i << '\n';
    }
};

int main(){
  X x = 20; // confusing!!
  x.print();
  return 0;
}

Use explicit keyword in order to prevent the converting constructor.

Destructor

destructor is provided for class unless user has provided one. Virtual destructors should be provided in order to achieve polymorphic behavior. The subclass object should be deleted with a base class pointer.

With a virtual destructor defined, the subclass destructor is called before base class.

Method overriding

  • if a sub-class defines a method with same name as from base class, then the method HIDES ALL the methods with the same name from base class (regardless of signatures of the methods.).
  • if a method is defined as a public method in base class and overridden as private or protected in the sub classes, it still will be publicly available.
  • Const and non const methods are different from each other.
  • Methods should be defined as ‘virtual’ in order to achieve polymorphic behavior.
    Example:

    class BaseClass{
      public:
      virtual void vprint(){
        std::cout << "in the BaseClass\n";
      }
      void print(){
        std::cout << "in the BaseClass\n";
      }
    };
    
    class SubClass: public BaseClass{
      public:
      virtual void vprint(){
        std::cout << "in the SubClass\n";
      }
      void print(){
        std::cout << "in the SubClass\n";
      }
    };
    
    void global_print(BaseClass & b){
      b.print(); //in the BaseClass
      b.vprint();// in the SubClass
    }
    
    int main(){
      BaseClass * b = new SubClass();
      b->print(); //in the BaseClass
      b->vprint(); // in the SubClass
    }
    
    

override keyword

c++11 introduced override keyword which can be used after method name in order to specify the method is overridden. If the keyword is not used, the virtual method can still be overridden in subclass. The advantages of override keyword:

  • override keyword ensures the method with same signature is defined in base class else it gives compilation error.
  • Its good documentation.

Method Overloading

  • Methods with (same name and ) default values to parameters are consider ambiguous. Example:
  •    int print(char x){
        std::cout << "in the print method that returns int\n";
       }
       int print(char x = 'c'){
          std::cout << "in the print method that returns int\n";
       }
    };

    Inheritance

  • Base class public member variables can not initialized using initializer lists.
  • class MyBaseClass{
      public:
      int _b;
    };
    
    class MyClass : public MyBaseClass{
      public:
      int _m;
      MyClass(int m): _m{m}, _b{0}{} // compiler error: 
    };
  • They should be instantiated by following ways:
  • class MyBaseClass{
      public:
      int _b;
      MyBaseClass(int b):_b{b}{}
    };
    
    class MyClass : public MyBaseClass{
      public:
      int _m;
      MyClass(int m, int b): _m{m}, MyBaseClass(b){}; // base class needs to have a parameterized constructor.
      // or MyClass(int m, int b): _m{m}{
      //    _b = b;
      //  };
    };

General method rules

  • constructors can not be virtual.
  • destructors should be virtual if the class is going to get inherited and polymorphic behavior is expected.
  • If a method takes non-const reference then a rvalue constant can not be passed to it. example:
  • void print(int& i){
      std::cout << i << '\n';
    }
    
    int main(){
      print(10);
      return 0;
    }
    
    //Solution: use constant reference like const int & i or int && i

     

  • Some methods like printing output to console using ostream << operator, the method must be defined as a friend method.
    class MyClass{
      int i;
      public:
        MyClass():i{18}{};
        friend std::ostream& operator << (std::ostream & o, MyClass & obj){
          o << obj.i << '\n';
        return o;
        }
    };
    
    int main(){
      MyClass m;
      std::cout << m << '\n';
      return 0;
    }
  • class defined with final keyword is not inheritable.
    class Class final{}; // this class is not inheritable now

Static Variables

  • static variables should be initialized outside the class definition. This is generally done in .cpp (implementation file). If the static members of class are not defined, then they generate an error like undefined member or reference. Example:
    class Class{
    public:
      static int count;
    
      void print(){
        count++;
        std::cout << count << '\n';
      }
    };
    
    int Class::count = 10;
    
    int main(){
      Class c;
      c.print();
      std::cout << Class::count<< '\n';
    }

Static Methods

  • Static methods can not be virtual because polymorphic behavior is limited to instances.
  • static methods can not be const else following error is generated.
    class StaticConst{
      static print() const {
        std::cout << "inside static const function \n";
      }
    };
    
    StaticConst::print(); // static member function cannot have 'const' qualifier
  • Static methods can have same name and signature as of non-static methods.

Sizeof Rules

  • if a class/struct does not have any member variables in it, its size is generally 1.
  • if a class/struct has a virtual method(s) then its size increases by 8 bytes in 64 bit linux/unix system. So a class with no member variables and 1+ any number of virtual methods, the size is 8 bytes.
  • Static variables size is not considered in the size of class/object.

Example:

class Class{
  public:
  static int count;

  virtual void print(){
    std::cout << count << '\n';
  }
};

int Class::count = 10;

int main(){
  Class c;
  c.print();
  std::cout << sizeof(c)<< '\n'; //8
}

Const member variables

  • if the const variables are defined in a class, they must be initialized else using them causes compilation error. Example:
    class X{
      public:
      const int m, n;
    
      void print(){
      std::cout << "m = " << m << " n = " << n << '\n';
      }
    };
    
    // error: call to implicitly-deleted default constructor of 'X'
      X x;
    // default constructor of 'X' is implicitly deleted because field 'm' of const-qualified type 'const int' would not be initialized
        const int m, n;

Automatic type casting

c++ allows method overloading, means the methods can have a same name but should have different signature (different return type and parameters). The automatic type casting has issues in defining method names including constructors.

For an example:

  1. int – float and vice versa
  2. int – double and vice versa
  3. int – char and vice versa
  4. any user defined type conversion

because of the automated type casting, methods(including constructors) with same name and castable types does not compile, example:

following code does not compile:

class MyClass{
  int x;
public:
   int print(char x){
    std::cout << "in the print method that returns int\n";
    return 0;
   }

   char print(char x){
    std::cout << "in the print method that returns char\n";
    return 'c';
   }

   double print(char x){
    std::cout << "in the print method that returns double\n";
    return 0.0;
   }
};

// Solution: use templates in place of defining so many methods with
// automatic type casted methods.

Initializer list

  • Initializer list must be used when using following types of member variable
    • the member variable is const like const int or const char
    • the member variable is a reference
    • initialization of base class.
    • name of variable is same as member function

Default and Delete keywords

  • c++11 provides default and delete keywords to control the method declaration.
  • non-parameterized constructors can be defined using default method as
    class MyClass{ 
      int x; 
      public:
        MyClass() = default; // this class has both constructors
        MyClass(char x){ };
  • Delete keyword introduced in c++11 is very useful like
    • making objects only movable and not copyable
    • implementing very famous design pattern like singleton.
    • deleting operator overridden methods for various purposes.
      • delete & operator method to stop users from taking address of objects.