Appearance
C++ as Its Own Language
C++ can call C APIs and retains much C-like syntax, but idiomatic C++ changes how resources, collections, errors, and abstraction are represented. Translating a C program mechanically into classes misses the language's strongest safety properties.
Prefer values that manage themselves
Compare a manual buffer with an owning standard-library value:
cpp
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<int> values{3, 5, 8};
std::string label = "total";
int total = 0;
for (int value : values) total += value;
std::cout << label << ": " << total << '\n';
}vector owns a dynamic sequence; string owns text storage. Their destructors release resources automatically when scope ends, including during exception unwinding. Copying them copies values; moving can transfer resources efficiently. This is RAII: resource lifetime follows object lifetime.
Raw arrays, malloc, and manual cleanup remain available but are not neutral defaults. Prefer vector, array, string, and resource-managing types unless an interface or measured constraint requires lower-level storage.
C compatibility is a boundary, not an architecture
C++ can include many C headers and call C linkage functions. A wrapper can translate a C handle into a class that enforces destruction:
cpp
class Report {
public:
Report();
~Report();
Report(const Report&) = delete;
Report& operator=(const Report&) = delete;
private:
report_handle* handle_;
};The wrapper should establish one ownership rule and preserve error detail. It should not add getters, inheritance, or heap allocation merely to look object-oriented.
Stronger constructs replace common C conventions
nullptrreplaces ambiguous null integer constants.- references express required aliases; pointers can represent optional or reseatable relationships.
- scoped
enum classvalues avoid leaking enumerator names and implicit integer conversions. - function overloads and templates replace many macro tricks.
- constructors establish invariants; destructors release resources.
- namespaces replace prefixes as the primary source-level naming mechanism.
- exceptions or typed result values can propagate errors without sentinel collisions.
These features do not eliminate low-level behavior. Object lifetime, invalidation, data races, undefined behavior, compilation, linking, and ABI constraints still matter.
Avoid treating every object as a reference object
Java and C# make class instances reference-oriented by default. C++ class objects are values unless accessed indirectly:
cpp
struct Point { double x; double y; };
Point translated(Point point, double dx, double dy) {
point.x += dx;
point.y += dy;
return point;
}This can be efficient through moves and copy elision. Do not allocate every object with new. Value semantics simplify lifetime, locality, concurrency, and testing.
Swift developers will recognize value-oriented design, but C++ copy/move behavior, reference invalidation, and deterministic destruction follow C++ rules rather than Swift's exclusivity or copy-on-write conventions.
Header and template compilation remain visible
C++ still uses translation units and linking. Templates are commonly defined in headers because instantiation needs the definition. Inline functions, the one-definition rule, name mangling, and modules add C++-specific constraints atop the pipeline learned earlier.
Optional prompts
Explain: Why is allocating every class with new an imported habit rather than idiomatic C++?
Answer: C++ objects have direct value semantics and deterministic destruction. Automatic or containing-object storage often expresses ownership more clearly and avoids separate allocation and manual lifetime management.
Explain: Is a C++ wrapper around a C API useful only for object-oriented style?
Answer: No. Its strongest purpose is usually invariant and resource management: construction checks acquisition, destruction releases exactly once, and copy/move policy documents ownership.