Design Vector

Design a customized vector container.

Introduction

I am launching a fresh blog series dedicated to object-oriented design with C++. The inspiration behind this initiative stems from my job interviews, where over 70% of the positions requiring C++ skills focused on designing scenarios rather than typical leetcode-style problems. This experience highlighted the significance of understanding design patterns and the principles of object-oriented programming (OOP).

Today, we will explore how to design a vector container in C++. Some basic requirements for our customized vector would be:

  • pushback(): push an object/element to the end of the vector
  • popback(): pop an object/element for the end of the vector
  • size(): get the size of the vector
  • emplaceback(): contruct an object in-place and push it to the end of the vector
  • []: vector indexing
  • clear(): deallocate the memory

Other requirements:

  • dynamic memory size allocation
  • manage all the allocated memory
  • adapt to different data types

Code

Vector.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#pragma once

#include <new>

template<typename T>
class Vector {
public:
Vector() {
ReAlloc(2);
}

~Vector() {
clear();
::operator delete(_data, _capacity * sizeof(T));
}

void pushback(const T& value) {
if (_size >= _capacity) {
ReAlloc(_capacity + _capacity / 2);
}

new(&_data[_size]) T(value);
_size++;
}

void pushback(T&& value) {
if (_size >= _capacity) {
ReAlloc(_capacity + _capacity / 2);
}

new(&_data[_size]) T(std::move(value));
_size++;
}

void popback() {
if (_size > 0) {
_size--;
_data[_size].~T();
}
}

void clear() {
for (size_t i = 0; i < _size; i++) {
_data[i].~T();
}

_size = 0;
}

template <typename... Args>
T& emplaceback(Args&&... args) {
if (_size >= _capacity) {
ReAlloc(_capacity + _capacity / 2);
}

// replacement new, construct objetcs in-place
new(&_data[_size]) T(std::forward<Args>(args)...);

return _data[_size++];
}

T& operator[](size_t index) {
return _data[index];
}

const T& operator[](size_t index) const {
if (index >= _size) {
__builtin_trap();
}
return _data[index];
}

size_t size() const {return _size;}

private:
void ReAlloc(size_t newCapacity) {
// 1. allocate a new block of memory
// 2. copy//move old data into new block
// 3. delete old data

T* newBlock = (T*) ::operator new(newCapacity * sizeof(T));

// for downsize the vector
if (newCapacity < _size) {
_size = newCapacity;
}
for (size_t i = 0; i < _size; i++) {
new (&newBlock[i]) T(std::move(_data[i]));
}
for (size_t i = 0; i < _size; i++) {
_data[i].~T();
}

::operator delete(_data, _capacity * sizeof(T));
_data = newBlock;
_capacity = newCapacity;
}

private:
T* _data = nullptr;

size_t _size = 0;
size_t _capacity = 0;
};

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
template <typename T>
void printVector(const Vector<T>& vec) {
for (size_t i = 0; i < vec.size(); i++) {
std::cout << vec[i] << std::endl;
}

std::cout << "-----------\n";
}

int main() {
Vector<std::string> vec;
vec.pushback("C++");
vec.pushback("Kotlin");
vec.emplaceback("Mojo");
vec.emplaceback("Python");

printVector(vec);

vec.popback();

printVector(vec);

vec.clear();

printVector(vec);

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
output:
C++
Kotlin
Mojo
Python
-----------
C++
Kotlin
Mojo
-----------
-----------
Author

Joe Chu

Posted on

2023-12-14

Updated on

2024-03-24

Licensed under

Comments