重新认识 C++
分享 Modern C++ 的冰山一角的一角
开源软件协会 RC
Outline
- 热身
- Lambda 表达式
- RAII
- 智能指针
- 模板
提示
- 可能需要一些 C++ 基础
- 后面的内容默认使用 C++17 标准
- 并不是所有特性都要求 C++17,但这里不做区分
class Foo {
public:
Foo(int num) : number_(num) {
}
int get_number() const {
return number_;
}
private:
int number_ = 0;
};
const float PI = 3.14;
// 除了默认访问说明符之外和 class 没有区别
struct Bar {
float radius = 0.0;
float length = 0.0;
float get_volume() const {
return PI * radius * radius * length;
}
};
热身:class 和 struct
void *p1 = 0; // bad!
void *p2 = NULL; // bad! same as (void *)0
void *p3 = nullptr; // good!
热身:new、delete、nullptr
Foo *foo = new Foo(2333);
int num = foo->get_number();
delete foo;
foo = nullptr; // avoid double free
int *arr = new int[233];
arr[232] = 1;
delete[] arr;
using namespace foo::baz::foo;
using foo::bar::A;
using B = foo::bar::B;
using foo::get_the_answer;
struct D : A {
using A::A;
};
热身:命名空间和 using 声明
namespace foo {
inline int get_the_answer() {
return 42;
}
namespace bar {
struct A {};
class B {};
} // namespace bar
namespace baz::foo {
enum class C { HELLO, WORLD };
} // namespace baz::foo
} // namespace foo
热身:auto 和 decltype
auto generate_map() {
std::unordered_map<std::string, std::pair<int, std::tuple<float, float, float>>> map;
map["foo"] = {1, {1.2, 1.3, 4.5}};
return map;
}
void modify_map(decltype(generate_map()) &map) {
map["bar"] = {2, {0.6, 7.8, 9.9}};
}
int main() {
auto i = 42;
auto b = true;
auto vec = std::vector<int>{};
auto map = generate_map();
modify_map(map);
}
Lambda 表达式
- 匿名函数
- 可赋值给变量或作为函数参数
- 可捕获外层作用域的变量(闭包)
Lambda 表达式:几个例子
struct Event {
std::string type, user_id, message;
};
auto is_valid = [](const Event &event) -> bool {
const static std::set<std::string> types{
"message",
"notice",
"request",
};
return types.count(event.type) > 0;
};
auto e1 = Event{"message", "张三", "你好"};
auto e2 = Event{"foo", "小明", ""};
assert(is_valid(e1) && !is_valid(e2));
auto gen_message_event = [] {
return Event{"message", "", ""};
};
auto e = gen_message_event();
assert(e.type == "message");
std::string s = "Hello, world!";
auto pos = std::find_if(
s.cbegin(),
s.cend(),
[](auto ch) {
return ch == ',' || ch == '!';
}
);
assert(*pos == ',');
Lambda 表达式:几个例子
std::string s = "Hello, world!";
auto pos = std::find_if(
s.cbegin(),
s.cend(),
[](auto ch) {
return ch == ',' || ch == '!';
}
);
assert(*pos == ',');
enum Direction { TOP = 0, RIGHT, BOTTOM, LEFT, INVALID };
auto direction = TOP;
auto next_direction = [&direction] {
auto ret = direction;
direction = static_cast<Direction>(direction + 1);
if (direction == INVALID) direction = TOP;
return ret;
};
for (int i = 0; i < 10; i++) {
std::cout << next_direction() << std::endl;
}
RAII
- 资源获取即初始化(Resource Acquisition Is Initialization),一种编程风格
- 资源的生命周期和对象生命周期绑定
- 构造函数中获取资源、析构函数中释放资源
- 移动构造函数和移动赋值运算符中转移资源
RAII:在文件流中的应用
int read_n() {
std::ifstream in("n.txt"); // 创建对象时,在构造函数中获取资源(打开文件)
int n;
in >> n;
return n; // 离开作用域时,在析构函数中释放资源(关闭文件)
}
RAII:在锁中的应用
std::mutex m;
void lock_bad() {
m.lock();
// do something
m.unlock();
}
void lock_good() {
std::lock_guard<std::mutex> lk(m);
// do something
}
RAII:在锁中的应用
std::mutex m;
void lock_good_2() {
// do something
{
std::lock_guard<std::mutex> lk(m);
// do something with lock
}
// do something
}
智能指针
- 自动管理内存
- 有三种:unique_ptr、shared_ptr、weak_ptr
- 这里只介绍 shared_ptr
智能指针:Why?
struct Request {
std::string method;
std::string path;
Headers headers;
std::string body;
};
// 使用传统的 new、delete
auto request = new Request{.method = "POST", .path = "/", .body = "{}"};
// handle the request
delete request; // 如果忘记 delete,或 handle 时抛出异常,则内存泄漏
// 如果 Request 是 RAII 风格的类,遗漏 delete 将导致资源被占用,RAII 的优势瞬间丧失
智能指针:shared_ptr
struct Request {
std::string method;
std::string path;
Headers headers;
std::string body;
};
// 使用 shared_ptr
auto request = std::shared_ptr<Request>(
new Request{.method = "POST", .path = "/", .body = "{}"}
);
// handle the request
// 无需手动 delete,当引用计数降为 0 时会自动释放
智能指针:make_shared
struct Response {
Headers headers;
std::string body;
};
auto response = std::make_shared<Response>(); // 完全避免使用 new
智能指针:引用计数
{
std::shared_ptr<int> p = nullptr;
{
auto tmp = std::make_shared<int>(42); // tmp 持有指针,引用计数为 1
*tmp = 233;
p = tmp; // 指针复制给 p,引用计数为 2
} // 离开 tmp 的作用域,引用计数为 1
assert(*p == 233);
} // 离开 p 的作用域,引用计数为 0,销毁对象、回收内存
模板
- 类模板、函数模板、别名模板、变量模板
- 这里只介绍类模板和函数模板的最基础用法
模板:类模板
template <typename Elem>
class Vector {
public:
Vector() : data_(nullptr), size_(0) {}
Vector(size_t n) { data_ = new Elem[n], size_ = n; }
~Vector() { delete[] data_; }
// ...
private:
Elem *data_;
size_t size_;
};
Vector<int> int_vec;
Vector<Vector<double>> double_vec_vec;
模板:函数模板
template <typename T>
T sum(const std::vector<T> &vec) {
T sum{}; // 零初始化
for (const auto &item : vec) {
sum += item; // T 需实现 += 运算
}
return sum;
}
auto s1 = sum<double>(std::vector<double>{1, 2.5});
auto s2 = sum(std::vector<int>{111, 222}); // 从参数可以推断出 T 时,无需显式指定
总结
- C++ 是一门博大精深的语言,不止是「面向对象的 C」
- 限于时间和我的水平,这里只能介绍一点皮毛
- 模板相关内容值得深入学习
延伸阅读
重新认识 C++
By richardchien
重新认识 C++
- 1,486