C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
2: From C to C++: What's Different
If you're coming from a C background, your first instinct in C++ will be to write C code that just happens to compile with a C++ compiler. I see this all the time. You'll use malloc, you'll manage arrays of char, and you'll spend half your afternoon debugging a segmentation fault because you forgot a null terminator. While that code works, it's not C++. You're essentially driving a Ferrari but keeping the governor on the engine at 20 mph.
The Manual Struggle of C-style Buffers
Let's look at a common task: storing a list of usernames. In C, you're used to managing the memory yourself. You might allocate a pointer to pointers, then loop through and allocate a specific number of bytes for each string. It looks something like this:
// The "C way" in a C++ file
int count = 3;
char** usernames = (char**)malloc(count * sizeof(char*));
usernames[0] = strdup("Alice");
usernames[1] = strdup("Bob");
usernames[2] = strdup("Charlie");
// ... use the data ...
for(int i = 0; i < count; i++) free(usernames[i]);
free(usernames);
Now, this feels natural if you've spent years in C, but it's a minefield. I've lost countless hours to "off-by-one" errors where I didn't account for the \0 character, or worse, an early return statement that bypassed the free() calls, creating a memory leak. You're burdened with the "bookkeeping" of the memory, which has nothing to do with the actual logic of your program.
Letting the Standard Library Handle the Heavy Lifting
In C++, we shift the responsibility of memory management from the programmer to the object. We use std::vector for the list and std::string for the text. This isn't just about typing fewer characters; it's about a concept called RAII (Resource Acquisition Is Initialization). Essentially, when the object goes out of scope, it cleans up after itself automatically.
// The C++ way
#include <vector>
#include <string>
std::vector<std::string> usernames = {"Alice", "Bob", "Charlie"};
// No malloc, no strdup, and absolutely no manual free().
Look at the difference. The std::vector knows how many elements it holds, and the std::string knows how long it is. If you need to add a fourth name, you just call usernames.push_back("Dave"). In the C version, you'd have to call realloc, hope it didn't fail, and potentially move your entire data structure in memory. It's a massive reduction in cognitive load.
Where the Trade-off Actually Sits
Now, you might be wondering if this "magic" comes at a cost. A purist will tell you that std::vector and std::string have a small amount of overhead compared to a raw pointer. In 99% of application code, this is irrelevant. The CPU cycles you save by using malloc are dwarfed by the engineering hours you waste debugging a memory leak that only happens once every thousand executions.
The real "cost" is a mental shift. You have to stop thinking about where the data lives in RAM and start thinking about who owns the data. In the C example, you owned the memory and were responsible for its death. In the C++ example, the vector owns the strings, and the vector's lifetime determines when the memory is reclaimed. Once you trust the containers, you can actually focus on solving the problem instead of babysitting the heap.
📋 Practical Task
Refactoring the Legacy User Registry
You've been handed a legacy snippet of code written by a developer who refused to leave C behind. The code below manages a simple registry of usernames but is riddled with manual memory management and risky strcpy calls. Your task is to refactor this into modern C++.
// LEGACY CODE TO FIX
#include <iostream>
#include <cstdlib>
#include <cstring>
void print_users(char** users, int count) {
for(int i = 0; i < count; i++) {
std::cout << users[i] << std::endl;
}
}
int main() {
int count = 2;
char** users = (char**)malloc(count * sizeof(char*));
users[0] = (char*)malloc(10 * sizeof(char));
strcpy(users[0], "Alice");
users[1] = (char*)malloc(10 * sizeof(char));
strcpy(users[1], "Bob");
print_users(users, count);
for(int i = 0; i < count; i++) free(users[i]);
free(users);
return 0;
}
Requirements:
- Replace the
char**andmalloclogic with astd::vector<std::string>. - Update the
print_usersfunction to accept the vector by reference (e.g.,const std::vector<std::string>& users) to avoid copying the entire list. - Remove all calls to
free()andstrcpy(). - Add a third user to the list using the
.push_back()method.
There are no comments for now.