C++ 类使用注意事项

前言 本文包含一些在使用 C++ 类时的注意事项,可避免一些常见问题,并能让你在写代码时更加自信。 因为目前我使用的是 C++ 17,所以仅保证以下内容在 C++ 17 中正确。 构造函数 对于单个参数的构造函数,推荐添加 explicit 关键字,防止隐式转换错误调用构造函数。 class DemoClass { explicit DemoClass(int test) { this->test_ = test; } } 显式声明有参构造函数后,编译器不会自动生成无参构造函数,若需要,请添加 DemoClass() = default; class DemoClass { explicit DemoClass(int test) { this->test_ = test; } DemoClass() = default; } 若子类中没有显式调用父类的构造函数,子类会默认调用父类的无参构造函数,如果父类没有无参构造函数,会报错。 析构函数 基类析构函数应该声明为 virtual,这样可以防止子类无法正确的析构。 由于基类析构函数为 virtual,派生类的析构函数应该显式的 override。 class DemoClass { virtual ~DemoClass() = default; } class ChildClass: public DemoClass { ~ChildClass() override { xxx; } } 成员变量默认值 对于类或局部作用域中未明确指定默认值的成员变量,其值遵循以下规则: 对于原生类型(primitive types),即 int、float、int*、string* 等,默认值为随机的脏数据。 对于对象(objects),例如自定义类、或 std::string 等,会调用默认(无参)构造函数,若没有默认(无参)构造函数,则报错。 对于引用(reference)类型,例如 std::string&,必须赋初始值或在构造函数中初始化,若为初始化,则报错。 float age; // 脏数据、随机值(无意义),此时访问会出现未定义行为 int *ptr; // 脏数据、随机值(无意义),此时访问会出现未定义行为 string name; // 调用默认构造函数,对于 std::string 来说为空字符串 "" DemoClass demo; // 若 DemoClass 存在无参构造函数,则调用,否则编译错误 string *pname; // 脏数据、随机值(无意义),此时访问会出现未定义行为 string &rname; // 编译错误,必须显式初始化 const string &crname; // 同上,编译错误 成员变量初始化方式 初始化成员变量一般而言有四种方式,一般而言第二种和第四种方式为等价的,不过在下面我们会看到一种例外: ...

November 9, 2022 · 2 min · zzsqwq

为什么要使用条件变量?

为什么要使用条件变量? 前言 最近看了很多与线程有关的 C++ 新特性,条件变量是见的比较多的一个特性。 看的时候我发现,想要理解一个新的特性,关键的要看它的引入到底解决了哪些问题,没有什么特性我们要实现相同的功能要怎么做? 以我的理解来看,条件变量是一个线程间互相同步与通知的手段,他通过主动唤醒的方式减小了各个线程的开销,取代了简单但是消耗较大的一直被动循环检验与等待。 没有条件变量我们如何实现相同的需求? 这里采用现代C++教程1 中关于条件变量的一个例子作为基础: 不使用条件变量版本 #include <queue> #include <chrono> #include <mutex> #include <thread> #include <iostream> #include <condition_variable> int main() { std::queue<int> produced_nums; std::mutex mtx; // 生产者 auto producer = [&]() { for (int i = 0; ; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::unique_lock<std::mutex> lock(mtx); std::cout << "producing " << i << std::endl; produced_nums.push(i); } }; // 消费者 auto consumer = [&]() { while (true) { { std::unique_lock<std::mutex> lock(mtx); if(produced_nums.empty()) continue; } std::unique_lock<std::mutex> lock(mtx); // 短暂取消锁,使得生产者有机会在消费者消费空前继续生产 lock.unlock(); // 消费者慢于生产者 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); while (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; produced_nums.pop(); } } }; // 分别在不同的线程中运行 std::thread p(producer); std::thread cs[2]; for (int i = 0; i < 2; ++i) { cs[i] = std::thread(consumer); } p.join(); for (int i = 0; i < 2; ++i) { cs[i].join(); } return 0; } 使用条件变量版本 #include <queue> #include <chrono> #include <mutex> #include <thread> #include <iostream> #include <condition_variable> int main() { std::queue<int> produced_nums; std::mutex mtx; std::condition_variable cv; bool notified = false; // 通知信号 // 生产者 auto producer = [&]() { for (int i = 0; ; i++) { std::this_thread::sleep_for(std::chrono::milliseconds(900)); std::unique_lock<std::mutex> lock(mtx); std::cout << "producing " << i << std::endl; produced_nums.push(i); notified = true; cv.notify_all(); // 此处也可以使用 notify_one } }; // 消费者 auto consumer = [&]() { while (true) { std::unique_lock<std::mutex> lock(mtx); while (!notified) { // 避免虚假唤醒 cv.wait(lock); } // 短暂取消锁,使得生产者有机会在消费者消费空前继续生产 lock.unlock(); // 消费者慢于生产者 std::this_thread::sleep_for(std::chrono::milliseconds(1000)); lock.lock(); while (!produced_nums.empty()) { std::cout << "consuming " << produced_nums.front() << std::endl; produced_nums.pop(); } notified = false; } }; // 分别在不同的线程中运行 std::thread p(producer); std::thread cs[2]; for (int i = 0; i < 2; ++i) { cs[i] = std::thread(consumer); } p.join(); for (int i = 0; i < 2; ++i) { cs[i].join(); } return 0; } 这两段代码在效果上是等效的,都是一个生产者两个消费者。 ...

August 24, 2022 · 2 min · zzsqwq

C++大数类的实现

C++大数类设计思路 洛谷大数类的评测结果(开了氧气优化) 这个第四个点真的优化不过去了QAQ,24W的数据,丧心病狂 ...

April 6, 2020 · 5 min · zzsqwq