Four ideas separate idiomatic modern C++ from "C with classes": RAII, smart pointers, move semantics and templates.
RAII
RAII (Resource Acquisition Is Initialization) means an object acquires a resource in its constructor and releases it in its destructor. A local object's destructor always runs when it leaves scope, even if an exception passes through, so resources owned this way cannot leak.
#include <cstdio>
#include <stdexcept>
class File {
public:
File(const char* path, const char* mode) : handle_{std::fopen(path, mode)} {
if (!handle_) throw std::runtime_error("could not open file");
}
~File() { std::fclose(handle_); } // closed automatically at end of scope
File(const File&) = delete; // no copies: never close the same handle twice
File& operator=(const File&) = delete;
std::FILE* get() const noexcept { return handle_; }
private:
std::FILE* handle_;
};
void write_log() {
File f{"log.txt", "w"};
std::fputs("start\n", f.get());
// even if something below throws, f's destructor closes the file
}
The standard library works the same way: std::vector and std::string own memory, std::ifstream owns a file, std::lock_guard owns a mutex lock. Modern code rarely needs a bare new or delete.
Smart pointers
Heap objects should be owned by a smart pointer from <memory>.
#include <iostream>
#include <memory>
#include <string>
#include <vector>
struct Shape {
virtual ~Shape() = default;
virtual double area() const = 0;
};
struct Circle : Shape {
explicit Circle(double r) : r{r} {}
double area() const override { return 3.14159 * r * r; }
double r;
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1.0));
shapes.push_back(std::make_unique<Circle>(2.0));
for (const auto& s : shapes) std::cout << s->area() << '\n';
// every Circle is destroyed when shapes goes away
auto config = std::make_shared<std::string>("debug");
auto another = config; // reference count is now 2
std::cout << config.use_count() << '\n'; // 2
}
Default to std::unique_ptr; use std::shared_ptr only when ownership is genuinely shared. A function that merely uses an object should take a reference (such as const Shape&) or a plain pointer, not a smart pointer.
Move semantics
C++11 added move operations, which transfer a resource instead of duplicating it. A temporary, or an object marked with std::move, can hand over its internal buffer, so an expensive copy becomes a few pointer assignments. std::move itself moves nothing; it is a cast meaning "you may move from this".
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main() {
std::string big(1'000'000, 'x');
std::vector<std::string> list;
list.push_back(big); // copy: big is untouched
list.push_back(std::move(big)); // move: big's buffer is handed over
// big is now valid but unspecified; assign a new value before relying on it
big = "reused";
std::cout << list.size() << '\n'; // 2
}
When returning a local, write return x;, not return std::move(x);: the compiler already applies copy elision or an implicit move. And if a class doesn't manage a resource directly, let the compiler generate its copy and move operations (the Rule of Zero).
Templates and generic programming
Templates are functions and classes parameterized by type; the compiler generates a concrete version for each type you use. The standard containers and algorithms are all templates.
#include <concepts>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
template <typename T>
T max_of(const T& a, const T& b) {
return (a < b) ? b : a;
}
template <typename T>
class Stack {
public:
void push(T value) { items_.push_back(std::move(value)); }
T pop() { T top = std::move(items_.back()); items_.pop_back(); return top; }
bool empty() const { return items_.empty(); }
private:
std::vector<T> items_;
};
// C++20 concepts: only integer types are accepted
template <std::integral T>
T half(T value) { return value / 2; }
int main() {
std::cout << max_of(3, 7) << '\n'; // 7
std::cout << max_of(std::string{"a"}, std::string{"b"}) << '\n'; // b
Stack<int> s;
s.push(1);
s.push(2);
std::cout << s.pop() << ' ' << half(10) << '\n'; // 2 5
// half(1.5); // error: double does not satisfy std::integral
}
Template definitions must be visible where they are used, so they usually live in headers. C++20 concepts name a template's requirements and turn pages of instantiation errors into a short, readable message.
Summary
- RAII: give every resource an owning object and let its destructor clean up.
- Create heap objects with
std::make_unique, and use std::shared_ptr only for genuinely shared ownership. std::move only permits a move; reassign a moved-from object before relying on it.- Templates give type-independent code; concepts state what it needs.
Further reading
- cppreference.com: language and library reference, noting which standard added each feature.
- C++ Core Guidelines: modern C++ guidance edited by Bjarne Stroustrup and Herb Sutter.
- isocpp.org: the Standard C++ Foundation's site, with news and learning material.
0 条评论
登录 · 登录后即可发表评论。