Skip to Content
Course content

2: From C to C++: What's Different

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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** and malloc logic with a std::vector<std::string>.
  • Update the print_users function 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() and strcpy().
  • Add a third user to the list using the .push_back() method.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.