医疗网站建设,上海计算机考试网页制作,竞价外包运营,网页设计与制作项目教程陈义文《Effective Modern C》第3章 Moving to Modern C
一、区分圆括号 () 与大括号 {} #xff08;Item 7#xff09;
C11 引入统一初始化#xff08;brace‑initialization#xff09;#xff0c;即使用 {} 来初始化对象#xff0c;与传统的 () 存在细微差别#xff1a;避…《Effective Modern C》第3章 Moving to Modern C
一、区分圆括号 () 与大括号 {} Item 7
C11 引入统一初始化brace‑initialization即使用 {} 来初始化对象与传统的 () 存在细微差别避免窄化转换narrowing
int x1(3.5); // x1 3隐式截断
int x2{3.5}; // 编译错误防止窄化列表初始化优先级高于单参数构造
struct A { A(int); A(std::initializer_listint); };
A a1(1); // 调用 A(int)
A a2{1}; // 调用 A(std::initializer_listint)内置数组与聚合类型
std::vectorint v1(5, 10); // 五个元素每个值为 10
std::vectorint v2{5, 10}; // 两个元素5, 10建议
对基本类型和聚合类型优先使用 {}以获得更严格的类型检查和一致的语法对于只接受单一特定参数的构造明确使用 () 避免调用错误的初始化列表构造函数。二、优先使用 nullptr 而非 0 或 NULLItem 8问题
NULL 在不同平台下定义可能为 0 或 (void*)0带来类型模糊使用整型 0 传给重载函数时编译器难以区分指针重载与整数重载。解决
void f(int);
void f(char*);f(0); // 调用 f(int)
f(nullptr); // 调用 f(char*)建议
在所有指针上下文中使用 nullptr保证类型安全和重载解析明确。三、使用别名声明using替代 typedefItem 9typedef 限制
语法晦涩无法用于模板别名不易与模板参数一起阅读。using 别名
typedef std::mapstd::string, std::vectorint MapType;
// 改为
using MapType std::mapstd::string, std::vectorint;// 模板别名
templatetypename K, typename V
using MapOf std::mapK, V;建议
在新代码中一律采用 using既清晰又可与模板别名和别名模板配合使用。四、优先使用作用域枚举enum classItem 10传统枚举问题
枚举常量位于所在命名空间易与其他符号冲突默认可隐式转换为整型丢失类型安全。作用域枚举优势
enum Color { Red, Green, Blue }; // Red 与全局冲突
enum class Shape { Circle, Square }; // Shape::Circle无冲突int i Shape::Circle; // 错误不能隐式转换指定底层类型
enum class ErrorCode : uint8_t { OK 0, Fail 1 };建议
新枚举定义一律使用 enum class如需与整型交互可显式 static_cast。五、用已删除函数 delete替代私有未定义函数Item 11旧习惯
class NonCopyable {
private:NonCopyable(const NonCopyable);NonCopyable operator(const NonCopyable);
};仅在不定义函数时会在链接期报错且误报位置不直观。现代做法
class NonCopyable {
public:NonCopyable(const NonCopyable) delete;NonCopyable operator(const NonCopyable) delete;
};建议
对于不希望调用的函数使用 delete让编译器在编译期明确报错并指出源位置。六、重写虚函数时声明 overrideItem 12风险
虚函数签名微小变动会导致意外重载而非重写潜藏运行期错误。加上 override
struct Base { virtual void f(int); };
struct Derived : Base {void f(int) override; // 正确重写void f(double) override; // 编译错误函数签名不匹配
};建议
所有重写基类虚函数的派生类函数都显式标注 override。七、优先使用 const_iterator 而非 iteratorItem 13背景
在不需要修改容器元素时应使用只读迭代器以保证不被意外改变。示例
std::vectorint v {/*...*/};
for (auto it v.cbegin(); it ! v.cend(); it) {// it 为 const_iterator无法通过 *it 进行写操作
}建议
在遍历容器且不打算修改元素时始终使用 cbegin()/cend() 或手动指定 const_iterator。八、声明不会抛出异常的函数为 noexceptItem 14好处
编译器可据此做更激进的优化在容器扩容时若元素移动构造标记为 noexcept可避免回退到拷贝构造。示例
void swap(Buffer b1, Buffer b2) noexcept {using std::swap;swap(b1.data, b2.data);
}建议
默认将不会抛出异常的函数标注 noexcept使用 noexcept(expr) 形式当抛出与否依赖于表达式。九、尽可能使用 constexprItem 15作用
在编译期间求值提高性能构造常量对象、用作编译期上下文。示例
constexpr int factorial(int n) {return n 1 ? 1 : (n * factorial(n - 1));
}static_assert(factorial(5) 120, 错误);建议
对所有能在编译期求值的函数或构造函数加上 constexpr在 C14 及以后constexpr 函数可包含循环和更多语句。十、使常量成员函数线程安全Item 16问题
const 成员函数默认是线程安全的吗不是。const 只是保证不修改成员表面状态但底层可能修改缓存等。做法
对内部缓存、延迟初始化等涉及可变状态的数据成员使用 mutable 和适当的同步机制如 std::mutex或者在 const 函数中不使用可变共享状态。示例
class Data {
public:int get() const {std::lock_guardstd::mutex lg(m_);return cachedValue_;}
private:mutable std::mutex m_;int cachedValue_;
};十一、理解特殊成员函数的生成规则Item 17
C 会在未显式声明时自动生成默认构造、拷贝/移动构造、拷贝/移动赋值、析构函数规则复杂拷贝构造函数
如果显式声明了移动构造或拷贝赋值拷贝构造会被阻塞C11移动构造函数
如果显式声明了拷贝构造、拷贝赋值或析构移动构造会被阻塞析构函数
显式定义后依然会生成但会影响其他特殊成员函数生成。建议
对于需要自定义移动或拷贝行为的类最好同时声明并定义所有相关特殊成员函数Rule of Five如无需移动应显式 delete 移动构造与移动赋值利用 default 保留自动生成版本并在声明处表达意图。通过对以上十一个细则的深入理解与实践你将全面掌握现代 C 编程中的常见陷阱与最佳实践为编写高性能、类型安全、可维护的代码奠定坚实基础。