aboutsummaryrefslogtreecommitdiff
path: root/src/blog
diff options
context:
space:
mode:
Diffstat (limited to 'src/blog')
-rw-r--r--src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md4
-rw-r--r--src/blog/2024-12-26-taming-cpp-raii/taming-cpp-raii.md280
2 files changed, 284 insertions, 0 deletions
diff --git a/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md b/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md
index 3aef0c7..3181eb0 100644
--- a/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md
+++ b/src/blog/2024-04-30-taming-cpp-motivation/taming-cpp-motivation.md
@@ -1,5 +1,7 @@
1# Taming C++, episode 1: motivation 1# Taming C++, episode 1: motivation
2 2
3*This post is part of a [series](../../series)*
4
3C++ is pretty much the standard language in the software industry for 5C++ is pretty much the standard language in the software industry for
4any project where performance matters. I have used it a fair bit in 6any project where performance matters. I have used it a fair bit in
5the past - during my [IOI](https://ioinformatics.org) time and for 7the past - during my [IOI](https://ioinformatics.org) time and for
@@ -173,3 +175,5 @@ to continue studying it. I may or may not keep writing about this here:
173turning this into another blog series may be a good way to force myself 175turning this into another blog series may be a good way to force myself
174to do this regularly, but on the other hand it may not be interesting 176to do this regularly, but on the other hand it may not be interesting
175for my readers. We'll see! 177for my readers. We'll see!
178
179*Next in the series: [RAII](../2024-12-26-taming-cpp-raii)*
diff --git a/src/blog/2024-12-26-taming-cpp-raii/taming-cpp-raii.md b/src/blog/2024-12-26-taming-cpp-raii/taming-cpp-raii.md
new file mode 100644
index 0000000..88744bb
--- /dev/null
+++ b/src/blog/2024-12-26-taming-cpp-raii/taming-cpp-raii.md
@@ -0,0 +1,280 @@
1# Taming C++, episode 2: RAII
2
3*This post is part of a [series](../../series)*
4
5After publishing
6[the previous post on this topic](../2024-04-30-taming-cpp-motivation),
7I have not actually done much with C++, besides reading some more about
8it. However, a few weeks ago I attended a three-day C++ training at work,
9so I decided to pick up this blog series again (thus actually turning
10into a series).
11
12For this episode I decided to focus on one of the defining features
13of C++: resource management and RAII ("Resource Acquisition Is
14Initialization"). So in this post I'll talk about constructors and
15destructors and how to use them to make resource management safer than
16with C. I'll also discuss copy and move operations, which are part of
17the same family as constructors and destructors.
18
19Since this post does not want to be a C++ tutorial, I am going
20to explain little to none of the syntax. I hope it will be easy
21to follow along anyway, even for someone who does not know C++.
22In any case, you will find throughout this page many links to
23[cppreference.com](https://en.cppreference.com/w/), an incredible resource
24for all things C++.
25
26As for the previous post, I added some code in a
27[git repository](https://git.tronto.net/taming-cpp) with the
28examples discussed here.
29
30Let's start with one practical example!
31
32## Impress your friends with this simple trick
33
34Consider the following C++ program:
35
36```
37class BadType {
38 // Stuff
39};
40
41int main() {
42 BadType x;
43 return 0;
44}
45```
46
47This program does nothing. Well, almost nothing. It allocates a variable
48of a custom type called `BadType`. But then it does nothing with it,
49it just exits returning a 0 value (success). So, assuming it compiles,
50it can never fail, right?
51
52Right?
53
54No, of course not, otherwise I would not be asking. For example, the
55class BadType may contain a *member variable* (for C programmers:
56that's C++ for "struct field") too large to be
57[allocated on the stack](https://en.wikipedia.org/wiki/Stack-based_memory_allocation),
58such as an array of 100 million integers.
59
60But there is a more interesting way to make this program fail by only
61changing the definition of `BadType`. In fact, you can make this program
62do pretty much everything you want by changing `BadType`'s *constructor*
63to suit your needs (see
64[surprising-error.cpp](https://git.tronto.net/taming-cpp/file/raii/surprising-error.cpp.html)):
65
66```
67class BadType {
68 BadType() {
69 BadType recursion_hell;
70 }
71};
72```
73
74The program now compiles without errors, and it immediately crashes.
75And all it does is declaring variables! But in C++, every time a variable
76of a *class type* is declared without any explicit initialization value,
77the corresponding
78[default constructor](https://en.cppreference.com/w/cpp/language/default_constructor)
79is called. And declaring a variable of `BadType` inside its own default
80constructor produces an artistic infinite recursion.
81
82And it does not stop here either: whenever a variable
83of a class type goes out of scope, the corresponding
84[destructor](https://en.cppreference.com/w/cpp/language/destructor)
85is called - see
86[constructor-hello-world.cpp](https://git.tronto.net/taming-cpp/file/raii/constructor-hello-world.cpp.html):
87
88```
89#include <iostream>
90
91class BadType {
92public:
93 BadType() {
94 std::cout << "Variable created!" << std::endl;
95 }
96
97 ~BadType() {
98 std::cout << "Variable destroyed, bye bye" << std::endl;
99 }
100};
101
102int main() {
103 BadType x;
104 return 0;
105}
106```
107
108Of course, printing silly messages is not the point of constructors
109and destructors. The point is managing resources such as memory,
110files and network connections.
111
112But before we get into that, let's take a moment to reflect on this
113example. For me, the main point here is that C++ does a lot of stuff
114under the hood. This is, as most things in C++, a double-edged sword:
115on the one hand, you can implement all sorts of interesting mechanisms
116of initialization and clean-up for your custum types; on the other hand,
117you constantly have to keep in mind that all of this stuff exists just
118to get the hang of a simple C++ program.
119
120Ok, now let's talk about resource management!
121
122## Resource Acquisition Is Initialization (RAII)
123
124[RAII](https://en.cppreference.com/w/cpp/language/raii) is a resource
125management technique widely used in C++, but also in some other
126languages. My personal interpretation is this: ONLY allocate resources
127with `malloc()`, `new`, `fopen()` and other dangerous operations *in
128constructors*, and ONLY de-allocate them with the respective `free()`,
129`delete`, `fclose()` and other dangerous operations in the respective
130*destructors*.
131
132Let's see a classic example. Say you have a function `f()` that, for
133some reason, needs to work with a large array locally. If you allocate
134it on the heap with `new` or `malloc()`, you must remember to `delete`
135or `free()` it in every place where the function returns:
136
137```
138bool f(unsgined big_number) {
139 // In C: int *a = malloc(big_number * sizeof(int));
140 int *a = new int[big_number];
141
142 if (/* some condition */) {
143 // Remember to release the memory here!
144 delete[] a;
145 return false;
146 }
147
148 // Do stuff...
149
150 // Also here!
151 delete[] a;
152 return true;
153}
154```
155
156In C, a clean way to do this is to use the `goto` statement (yes, I know,
157[considered harmful](https://en.wikipedia.org/wiki/Considered_harmful)
158blah blah) more or less like this:
159
160```
161bool f(unsgined big_number) {
162 bool return_value = true;
163 int *a = malloc(big_number * sizeof(int));
164
165 if (/* some condition */) {
166 return_value = false;
167 goto f_cleanup_and_return;
168 }
169
170 /* Do stuff... */
171
172f_cleanup_and_return:
173 free(a);
174 return return_value;
175}
176```
177
178Which is all fine and good, but wouldn't it be better if this
179de-allocation happened automatically based on the scope of the pointer
180`a`, just like if we had allocated it on the stack? This can be achieved
181in C++ using constructors and destructors, for example:
182
183```
184class ArrayThing {
185public:
186 // Constructor
187 ArrayThing(unsigned n) {
188 buffer = new int[n];
189 }
190
191 // Destructor
192 ~ArrayThing() {
193 delete[] buffer;
194 }
195
196 // You probably want something like this:
197 int& operator[](unsigned i) {
198 return buffer[i];
199 }
200private:
201 int *buffer;
202};
203
204bool f(unsigned big_number) {
205 ArrayThing a(big_number);
206
207 if (/* some condition */) {
208 return false; // Destructor is called, a is cleaned!
209 }
210
211 // Do stuff...
212
213 return true; // Destructor is called, a is cleaned!
214}
215```
216
217And this, as far as I understand it, is the essence of RAII. The same
218concept applies not only to memory allocation, but also to other
219resource-management operations, such as opening files or locking
220a [mutex](https://en.wikipedia.org/wiki/Lock_(computer_science)).
221
222The example above is only for illustrative purposes: in practice
223if you want to achieve this result you should use a standard library
224container such as
225[`std::vector`](https://en.cppreference.com/w/cpp/container/vector)
226or
227[`std::array`](https://en.cppreference.com/w/cpp/container/array);
228but these standard classes do pretty much the same thing under the hood.
229
230## Copying and moving
231
232So far I have only talked about constructors and destructors, but C++
233offers control over two other mechanisms: *copy* and *move*. Both of
234these come in two forms, a *constructor* form and an *assignment* form.
235
236Copy and move operations can be summarized as follows:
237
238* **Copy** is the operation that consists of creating or assigning a
239 `target` object from a `source` object of the same type, copying the
240 value of the source into the target. They act similarly to a regular
241 constructor; a copy assignment must also take care of cleaning up
242 the resources of the target object before copying the value.
243* **Move** is the operation that consists of creating or assigning a
244 `target` object from a `source` object of the same **and then immediately
245 destroying the source object**, moving the value of the source into the
246 target. They act both as constructors for `target` and as destructors
247 for `source`; a move assignment must also take care of cleaning up
248 the resources of the target object before moving the value.
249
250Copy operations happen whenever you create an object from another one,
251for example with `T a(b)` or `a = b`. Move operations are perhaps a bit
252harder to understand, but they also happen regularly; returning an object
253from a function is a classic example, but they also come up when using
254[smart pointers](https://en.cppreference.com/book/intro/smart_pointers).
255
256I made a
257[comprehensive example](https://git.tronto.net/taming-cpp/file/raii/all-constructors.cpp.html)
258of how all of these operations work, so you can see when exactly each
259of them is called. Do check it out if you are interested!
260
261Finally, I have tried summarizing the construction, destruction, copy
262and move operations in the table below:
263
264|Operation |Signature |Construct target|Destroy (old) target|Destroy source|
265|:------------------------------------------------------------------------------|:------------------|:--------------:|:------------------:|:------------:|
266|Constructor |`T(...)` |✓ |N/A |N/A |
267|[Destructor](https://en.cppreference.com/w/cpp/language/destructor) |`~T()` |❌ |✓ |N/A |
268|[Copy constructor](https://en.cppreference.com/w/cpp/language/copy_constructor)|`T(T&)` |✓ |N/A |❌ |
269|[Copy assignment](https://en.cppreference.com/w/cpp/language/copy_assignment) |`T& operator=(T&)` |✓ |✓ |❌ |
270|[Move constructor](https://en.cppreference.com/w/cpp/language/move_constructor)|`T& T(T&&)` |✓ |N/A |✓ |
271|[Move assignment](https://en.cppreference.com/w/cpp/language/move_assignment) |`T& operator=(T&&)`|✓ |✓ |✓ |
272
273## Conclusion
274
275Manual resource management (in particular, memory management) and RAII
276are defining features of C++, features that clearly set it apart from
277other object-oriented languages like Java or C#. C++ gives you a lot
278of control over the low-level details, and some powerful tools to make
279use of it, in exchange for a lot of complexity that you must, at the
280very least, be aware of.

Generated with cgit - Back to sebastiano.tronto.net