I have solid beginner-to-intermediate experience with C, including one fairly substantial project, and I've also used Python, Rust, JavaScript, and a little C#. I learn best by building projects and looking up syntax as needed, but C++ feels unusually broad: there are often several ways to solve the same problem, and valid C code can also compile as C++.
I'd like to learn modern C++ practices instead of gradually creating a messy hybrid of C and C++. I'm especially unsure about classes, inheritance, object lifetime, pointers versus references, and when to use features such as containers, smart pointers, or optional values. I understand the general idea of structs and methods from Rust, but I know that doesn't map perfectly to C++ classes.
What are the biggest pitfalls for someone coming from C? Are there practical rules, project ideas, or learning resources that can help me recognize when I'm using outdated or unnecessarily low-level techniques? I learn much better by applying concepts than by reading a huge amount of theory, though I'm willing to study the essentials.
3 Answers
C++ has a very large language surface, so some reading is unavoidable if you want to understand both modern code and older codebases. You don’t need to memorize every feature, but you should learn the core ideas: value semantics, RAII, const-correctness, references, move semantics, standard containers and algorithms, and clear ownership rules.
A useful rule of thumb is that a raw pointer usually means either “this can be absent,” “this is not owned,” or “I am doing deliberately low-level work.” If none of those are true, a reference or an ordinary object may be clearer. Use std::unique_ptr for exclusive ownership, std::shared_ptr only when shared lifetime is genuinely needed, and std::optional when a value may be missing. Also pay attention to compiler warnings and use tools such as sanitizers and a formatter while working on projects.
Classes are not mandatory, and inheritance should not be treated as the next level after structs. In C++, a class is often just a way to combine data with the functions that operate on it, control access, and guarantee initialization. Many useful types can remain simple structs with public data. Prefer composition—objects containing other objects—unless you truly need substitutable polymorphic types.
Coming from C, look for repeated functions that all take the same structure pointer, manual initialization that must happen in a particular order, or cleanup code that callers frequently forget. Those are situations where a class and RAII can make the design safer and easier to use.
The biggest trap is writing C-style code and only adding C++ syntax around it. While learning modern C++, deliberately prefer the standard library: use std::vector instead of manually managed arrays, std::string instead of character buffers, range-based for loops instead of index-heavy loops, and references when an argument must exist and does not need to be reseated. If you find yourself reaching for malloc, free, or raw new, first check whether a container, value type, or smart pointer already expresses the ownership more clearly.
For classes, start with small types that protect their own invariants and manage their own resources. Constructors can ensure an object starts in a valid state, and member functions can keep operations close to the data they affect. Don’t assume inheritance is required—composition is often simpler. Build something small, such as an inventory or library system, and let the design problems show you which features are useful.

That seems like a good balance: avoid deliberately writing C, but still learn the lower-level features when a project actually needs them. Starting with small value-like classes also sounds less intimidating than trying to design a large inheritance hierarchy.