Notes
C Plus Plus
C Plus Plus
  • Roadmap
    • 🛣️C Plus Plus: Docs
  • Build
    • 🔥Build Process
    • 🔧Connect multiple C++ Files
  • Code
    • ⏮️Pre-Processors
      • #include
      • #define
      • #ifdef
      • #pragma
      • Predefined Macros
    • LValue and RValue
    • 🪅Data Types
      • Enum
      • TypeDef
      • const in c++
      • extern vs inline
    • 🎭Casting
    • 🔃Operator overloading
      • Available Operators
      • Examples of operator overloading
    • 🗺️Namespace
      • Namespace Example
      • Using directive
    • 🕵️Header File
      • For C++ Classes
    • 🏗️Structure
      • Struct vs Class
      • Public vs Private inheritance
    • 🏢Classes
      • Friend Function
      • Copy Constructor
      • Explicit Constructor
      • Move Constructor
        • Move Semantics
      • Other constructors
      • Virtual functions
      • Pure virtual function
      • Other function declaration
      • const function vs final function
  • Memory
    • 🧠Memory Introduction
    • ✨Heap and Stack
    • 🎯Pointers
      • Dangling Pointer
      • 'this' Pointer
      • Function Pointer
      • Smart Pointers
        • Unique Pointer
        • Shared Pointer
        • Weak Pointer
      • Reference count
    • 👨‍🏭Helper function
    • 🍡Vector [ArrayList]
      • Custom vector, part 1
      • Custom vector, part 2
      • Custom vector, part 3
      • std::vector
    • ♻️Union
      • Type Punning
      • Type Punning, part 2
      • Type Punning, part 3
      • Union, part 1
      • Union, Part 2
  • Thread
    • 🧵Threading
      • std::thread
      • Detach a thread
  • Misc
    • 🗂️Execution Order
    • 🧠Print memory
Powered by GitBook
On this page
  1. Memory
  2. Vector [ArrayList]

Custom vector, part 3

As we have seen in Custom vector, part 2, using array operations, we are coping each element on by one and repeat once we are out of capacity.

We can directly operate on memory using malloc, memcpy and realloc operations.

Here is a example:

#include <iostream>
#include <cstring>
#include <algorithm>

class MyInt {
private:
    int _int;

public:
    MyInt() : _int(0) {
        std::cout << "Default Constructor called for " << _int << ", address: " << &_int << std::endl;
    }

    MyInt(int a) : _int(a) {
        std::cout << "Constructor called for " << _int << ", address: " << &_int << std::endl;
    }

    ~MyInt() {
        std::cout << "Deconstructor called for " << _int << ", address: " << &_int << std::endl;
    }

    // Copy constructor
    MyInt(const MyInt& other): _int(other._int) {
        std::cout << "Copy constructor called for " << _int << ", address: " << &_int << std::endl;
    }

    // Move constructor
    MyInt(MyInt&& other) noexcept : _int(other._int) {
        std::cout << "Move constructor called for " << _int << ", address: " << &_int << std::endl;
    }

    void introduce() const {
        std::cout << "MyInt Value:  " << _int << ", address: " << &_int << std::endl;
    }

    MyInt& operator=(const MyInt& other) {
        if (this != &other) {
            _int = other._int;
        }
        return *this;
    }

    friend std::ostream& operator<<(std::ostream& os, const MyInt& obj);
};

std::ostream& operator<<(std::ostream& os, const MyInt& obj) {
    os << "value: " << obj._int;
    return os;
}

template<typename T>
class CustomVector {
private:
    T* data;
    size_t size;
    size_t capacity;

    void realloc(size_t newCapacity) {
        T* newData = static_cast<T*>(malloc(newCapacity * sizeof(T)));
        if (newData == nullptr) {
            throw std::bad_alloc();
        }
        
        if (data != nullptr) {
            memcpy(newData, data, size * sizeof(T));
            free(data);
        }
        
        data = newData;
        capacity = newCapacity;
    }

public:
    CustomVector() : data(nullptr), size(0), capacity(0) {}

    ~CustomVector() {
        clear();
        if (data != nullptr) {
            free(data);
        }
    }

    void push_back(const T& value) {
        if (size == capacity) {
            size_t newCapacity = (capacity == 0) ? 1 : capacity * 2;
            realloc(newCapacity);
        }
        memcpy(data + size, &value, sizeof(T));
        ++size;
    }

    void pop_back() {
        if (size > 0) {
            --size;
        }
    }

    T& operator[](size_t index) {
        if (index >= size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }

    const T& operator[](size_t index) const {
        if (index >= size) {
            throw std::out_of_range("Index out of range");
        }
        return data[index];
    }

    size_t get_size() const {
        return size;
    }

    size_t get_capacity() const {
        return capacity;
    }

    void clear() {
        size = 0;
    }

    // Custom iterator implementation
    class Iterator {
    private:
        T* ptr;
    public:
        Iterator(T* p) : ptr(p) {}
        T& operator*() { return *ptr; }
        Iterator& operator++() { ++ptr; return *this; }
        bool operator!=(const Iterator& other) const { return ptr != other.ptr; }
    };

    Iterator begin() { return Iterator(data); }
    Iterator end() { return Iterator(data + size); }
};

// Example usage
int main() {
    CustomVector<MyInt> v;
    std::cout << "add fist" << std::endl;
    v.push_back(MyInt(35));
    
    for (size_t i = 0; i < v.get_size(); ++i) {
        std::cout << "value: " << v[i] << ", address: " << &v[i] << std::endl;
    }
    
    std::cout << "add second" << std::endl;
    v.push_back(MyInt(30));
    
    for (size_t i = 0; i < v.get_size(); ++i) {
        std::cout << "value: " << v[i] << ", address: " << &v[i] << std::endl;
    }
    
    std::cout << "add third" << std::endl;
    v.push_back(MyInt(25));

    for (size_t i = 0; i < v.get_size(); ++i) {
        std::cout << "value: " << v[i] << ", address: " << &v[i] << std::endl;
    }

    std::cout << "pop back" << std::endl;
    v.pop_back();

    for (size_t i = 0; i < v.get_size(); ++i) {
        std::cout << "value: " << v[i] << ", address: " << &v[i] << std::endl;
    }
    
    std::cout << "add fourth" << std::endl;
    v.push_back(MyInt(20));

    for (size_t i = 0; i < v.get_size(); ++i) {
        std::cout << "value: " << v[i] << ", address: " << &v[i] << std::endl;
    }

    return 0;
}

Output:

add fist
Constructor called for 35, address: 0x7fffffffd7d4
Deconstructor called for 35, address: 0x7fffffffd7d4
value: value: 35, address: 0x55555556b2c0
add second
Constructor called for 30, address: 0x7fffffffd7d4
Deconstructor called for 30, address: 0x7fffffffd7d4
value: value: 35, address: 0x55555556b2e0
value: value: 30, address: 0x55555556b2e4
add third
Constructor called for 25, address: 0x7fffffffd7d4
Deconstructor called for 25, address: 0x7fffffffd7d4
value: value: 35, address: 0x55555556b2c0
value: value: 30, address: 0x55555556b2c4
value: value: 25, address: 0x55555556b2c8
pop back
value: value: 35, address: 0x55555556b2c0
value: value: 30, address: 0x55555556b2c4
add fourth
Constructor called for 20, address: 0x7fffffffd7d4
Deconstructor called for 20, address: 0x7fffffffd7d4
value: value: 35, address: 0x55555556b2c0
value: value: 30, address: 0x55555556b2c4
value: value: 20, address: 0x55555556b2c8

Here, as compared to before, we have reduced number of memory operations.

But we still have a long way to go, unnecessary memory operations are still happening, we can stop them using something called as emplace_back provided by std::vector, which will allow us to handover class creating to Vector class itself, this reducing further more memory operations.

PreviousCustom vector, part 2Nextstd::vector

Last updated 9 months ago

Run it .

🍡
here