2.基础类型与工具类-c++ linux编程:从0实现muduo库系列
0 章节重点
重点内容
视频讲解:《C++Linux编程进阶:从0实现muduo C++网络框架系列》-第2讲.基础类型与工具类
代码改动
cp -r lesson1 lesson2
- 实现:base/Types.h
- 实现:base/copyable.h和noncopyable.h
- 实现:base/StringPiece.h
使用beyondcompare对比两个版本的改动
1 标记类不允许拷贝/允许拷贝(重点掌握)
1.1 添加的文件
1.1.1 base/noncopyable.h 不允许拷贝
#ifndef MYMUDUO_BASE_NONCOPYABLE_H #define MYMUDUO_BASE_NONCOPYABLE_H namespace mymuduo { /// @brief 标记类:表示继承自该类的类是不可拷贝的 class noncopyable { public: noncopyable(const noncopyable&) = delete; // 禁止拷贝构造 noncopyable& operator=(const noncopyable&) = delete;//禁止赋值 protected: noncopyable() = default; ~noncopyable() = default; }; } // namespace mymuduo #endif // MYMUDUO_BASE_NONCOPYABLE_H
在基类禁止拷贝构造函数,禁止赋值函数
继承这个基类的子类对象都不能进行拷贝复制。比如
// 不可拷贝的资源类 class DatabaseConnection : public noncopyable { public: DatabaseConnection(const std::string& connectionString) { // 建立数据库连接 } // 不需要显式删除拷贝构造和赋值运算符,继承自noncopyable已经实现了 };
数据库的连接,网络的连接,不能给多个对象同时使用。
1.1.2 base/copyable.h允许拷贝
#ifndef MYMUDUO_BASE_COPYABLE_H #define MYMUDUO_BASE_COPYABLE_H namespace mymuduo { /// @brief 标记类:表示继承自该类的类是可拷贝的 /// 这是一个空基类,不包含任何成员 /// 仅用于在语义上标记某个类是可以拷贝的 class copyable { protected: copyable() = default; // 默认构造函数 ~copyable() = default; // 默认析构函数 }; } // namespace mymuduo #endif // MYMUDUO_BASE_COPYABLE_H
目的是告诉其他人,如果子类继承了copyable这个基类,则说明子类的对象是可以拷贝赋值的。
default默认构造函数的作用:
在C++中约定如果一个类中自定义了带参数的构造函数,那么编译器就不会再自动生成默认构造函数,也就是说该类将不能默认创建对象,只能携带参数进行创建一个对象;
但有时候需要创建一个默认的对象但是类中编译器又没有自动生成一个默认构造函数,那么为了让编译器生成这个默认构造函数就需要default这个属性。
class A { public: // A() = default; A(int B) { b = B; } private: int b; }; int main() { A a; // 如果没有 A() = default; 这里会报错 return 0; }
1.2 测试范例
examples/test_copyable_noncopyable.cc
// B站程序员老廖 https://space.bilibili.com/3494351095204205 #include "base/copyable.h" #include "base/noncopyable.h" #include <string> using namespace mymuduo; // 不可拷贝的资源类 class DatabaseConnection : noncopyable { public: DatabaseConnection(const std::string& connectionString) { // 建立数据库连接 } // 不需要显式删除拷贝构造和赋值运算符,继承自noncopyable已经实现了 }; // 可以安全拷贝的值类型 class Point : copyable { public: Point(int x, int y) : x_(x), y_(y) {} private: int x_; int y_; }; int main() { DatabaseConnection db1("connection1"); // DatabaseConnection db2 = db1; // 编译错误:拷贝构造被禁止 // DatabaseConnection db3; // db3 = db1; // 编译错误:赋值操作被禁止 Point p1(1, 2); Point p2 = p1; // 正确:Point是可拷贝的 Point p3(3, 4); p3 = p1; // 正确:赋值操作允许 return 0; }
1.3 面试重点
1.noncopyable 类的好处:
- 防止意外拷贝:通过删除拷贝构造函数和赋值运算符,可以在编译期就阻止对象的拷贝
- 资源安全:适用于那些不应该被拷贝的资源类,比如:网络连接、数据库连接
- 编译期检查:错误在编译期就能被发现,而不是运行时
- 语义清晰:通过继承 noncopyable,代码的意图一目了然
- 代码复用:不需要在每个类中重复写 = delete 声明
2.copyable 类的好处:
- 语义标记:明确表明类是可以安全拷贝的
- 代码自文档化:提高代码可读性,使类的设计意图更清晰
- 接口一致性:与 noncopyable 形成对称的设计
- 可扩展性:如果将来需要为所有可拷贝类添加共同的功能,只需修改这个基类。
2 Types.h的作用
// 1. 引入一些定义到命名空间 // 2. 提供一些常用的函数 // 3. 提供一些常用的模板元编程
2.1 实现文件 Types.h
2.1.1 引入定义到命名空间
namespace mymuduo { using std::string; // 将std::string引入到mymuduo命名空间 using std::vector;// 也可以这么使用。 }
这样可以在mymuduo命名空间里直接使用string
using namespace mymuduo; // 现在可以直接使用string,而不需要写std::string void example() { string str1 = "hello"; // 等价于 std::string string str2("world"); // 不需要写std::string string str3 = str1 + str2; // 代码更简洁 }
2.1.2 工具函数
namespace mymuduo { inline void memZero(void* p, size_t n) { memset(p, 0, n); } }
简化将指向的内存块清0
2.1.3 类型转换
只允许安全的隐式转换
namespace mymuduo { //和muduo实现有区别 template<typename T> inline T implicit_cast(typename std::remove_reference<T>::type& x) { return x; } template<typename T> inline T implicit_cast(const typename std::remove_reference<T>::type& x) { return x; } }
implicit_cast 作用
只允许安全的、隐式的类型转换
- 在编译期捕获类型转换错误
- 使代码意图更清晰
- 提供了比 static_cast 更严格的类型安全性
- 特别适合在大型项目中使用,可以预防很多潜在的类型转换错误
提供了比 static_cast 更严格的类型安全性 范例:
// 错误示范:不能进行可能丢失数据的转换 int64_t big_num = 0x12345678ABCD; int32_t small_num1 = implicit_cast<int32_t>(big_num); // 编译错误:可能丢失数据 int32_t small_num2 = static_cast<int32_t>(big_num); // 编译正常
2.2 测试范例
examples/test_cast.cc
#include "base/Types.h" #include <iostream> using namespace mymuduo; // 基类 class Animal { public: virtual ~Animal() {} virtual void makeSound() { std::cout << "动物叫声" << std::endl; } }; void testImplicitCast() { std::cout << "=== 测试 implicit_cast ===" << std::endl; // 1. 基础类型转换 int32_t small = 42; int64_t big = implicit_cast<int64_t>(small); std::cout << "数值转换: " << big << std::endl; // 2. 错误示范 不能进行可能丢失数据的转换(注释掉的代码会导致编译错误) // 错误示范: int64_t big_num = 0x12345678ABCD; // int32_t small_num1 = implicit_cast<int32_t>(big_num); // 编译错误:可能丢失数据 int32_t small_num2 = static_cast<int32_t>(big_num); // 编译正常 } int main() { testImplicitCast(); return 0; }
2.3 面试重点
面试在讲解自己做的项目时,可以讲解:
- (扩展)对于类型转换,后续可以补充学习static_cast、reinterpret_cast、const_cast 和 dynamic_cast四种类型的区别和应用场景。
3 字符串片段类
设计StringPiece类的目的是减少数据的拷贝,
3.1 实现文件 StringPiece.h
base/StringPiece.h 节选重点片段
class StringPiece { private: // 核心成员变量 const char* ptr_; // 字符串数据的指针,const确保不会修改原始数据 int length_; // 字符串长度,避免重复strlen计算 public: // 1. 构造函数族 // 默认构造:空字符串 StringPiece() : ptr_(nullptr), length_(0) {} // 初始化为空视图 // C风格字符串构造 StringPiece(const char* str) : ptr_(str), length_(static_cast<int>(strlen(ptr_))) {} // 注意:这里会调用strlen,在构造时计算一次长度 // string对象构造,直接使用string的内部数据 StringPiece(const std::string& str) : ptr_(str.data()), length_(static_cast<int>(str.size())) {} // 使用string的data()和size(),避免strlen // 指定内存区域构造,用于已知长度的场景 StringPiece(const char* offset, int len) : ptr_(offset), length_(len) {} // 直接使用提供的长度,最高效 // 2. 现代C++支持:转换为string_view operator std::string_view() const { return std::string_view(ptr_, length_); // 零开销转换 } // 3. 核心访问接口 // 获取原始数据指针 const char* data() const { return ptr_; } // 获取长度 int size() const { return length_; } // 检查是否为空 bool empty() const { return length_ == 0; } // 4. 视图操作 // 清空视图 void clear() { ptr_ = nullptr; length_ = 0; } // 重设视图范围 void set(const char* buffer, int len) { ptr_ = buffer; length_ = len; } // 数组访问操作符 char operator[](int i) const { return ptr_[i]; } // 5. 视图修改操作(不修改原始数据) // 从前面移除n个字符 void remove_prefix(int n) { ptr_ += n; // 移动指针 length_ -= n; // 调整长度 } // 从后面移除n个字符 void remove_suffix(int n) { length_ -= n; // 只需调整长度 } // 6. 比较操作 // 相等性比较 bool operator==(const StringPiece& x) const { return ((length_ == x.length_) && // 先比较长度(快速失败) (memcmp(ptr_, x.ptr_, length_) == 0)); // 再比较内容 } // 不等性比较 bool operator!=(const StringPiece& x) const { return !(*this == x); } // 7. 转换操作 // 转换为string(会发生拷贝) std::string as_string() const { return std::string(data(), size()); } // 将内容拷贝到目标string void CopyToString(std::string* target) const { target->assign(ptr_, length_); } // 8. 实用功能 // 检查前缀 bool starts_with(const StringPiece& x) const { return ((length_ >= x.length_) && (memcmp(ptr_, x.ptr_, x.length_) == 0)); } };
性能关键点:
- 避免不必要的字符串拷贝
- 提高数据处理效率
并且兼容string类型
3.2 后续在项目中的应用
- 网络缓冲区Buffer类
- TcpConnection类
3.3 测试范例
examples/test_StringPiece.cc
#include "base/Types.h" #include "base/StringPiece.h" #include <iostream> #include <vector> #include <memory> using namespace mymuduo; // 测试StringPiece类 void testStringPiece() { std::cout << "\n=== 测试 StringPiece ===\n"; // 基本构造和操作 StringPiece empty; std::cout << "空StringPiece: " << (empty.empty() ? "是空的" : "不是空的") << std::endl; const char* literal = "Hello, World!"; StringPiece sp1(literal); std::cout << "从字面量构造: " << sp1.as_string() << std::endl; std::string str = "Hello, Muduo!"; StringPiece sp2(str); std::cout << "从string构造: " << sp2.as_string() << std::endl; // 测试remove_prefix和remove_suffix StringPiece sp3("Hello, World!"); sp3.remove_prefix(7); // 移除"Hello, " std::cout << "移除前缀后: " << sp3.as_string() << std::endl; sp3.remove_suffix(1); // 移除"!" std::cout << "移除后缀后: " << sp3.as_string() << std::endl; // 测试比较操作 StringPiece sp4("World"); std::cout << "sp3 == sp4: " << (sp3 == sp4 ? "true" : "false") << std::endl; } int main() { std::cout << "开始测试字符串判断类...\n"; testStringPiece(); return 0; }
3.4 面试重点
StringPiece的设计本质是为了在传递数据的时候减少中间环节的拷贝和释放,但需要较多开发经验理解这个设计的意义,所以建议不要主动讲这个设计思路,被问到如何优化字符串传递的开销时则可以讲解。
但如果是量化交易面试时,这些细节是可以主动给面试官讲解的,但前提是你真的理解了。
建议扩展:可以进一步对比C++17引入的string_view
4 章节总结
重点:
- 理解nocopyable/copyable基类的作用和意义(重点掌握)
- 类型转换问题:面试前要准备static_cast、reinterpret_cast、const_cast 和 dynamic_cast四种类型的区别和应用场景的八股文。