只使用统一初始化
C++的初始化实在太多了,C++11 出现后,只推荐使用统一初始化。
class MyClass {
public:
MyClass() { /*...*/ }
MyClass(int x, double y) { /*...*/ }
};
MyClass obj1{}; // 使用统一初始化调用默认构造函数
MyClass obj2{1, 3.14}; // 使用统一初始化调用带参数的构造函数
MyClass obj3{obj2}; // 使用统一初始化调用拷贝构造函数
MyClass&& createObject() {
MyClass temp{1, 3.14};
return temp;
}
// 使用统一初始化调用移动构造函数
MyClass obj4{createObject()};
尽管统一初始化在很多情况下都是合适的,但在某些特定场景中,你可能需要使用其他初始化方法。例如,当使用某些老旧的API或与旧代码库交互时,可能需要用到传统的初始化方法(如圆括号或等号)。此外,对于某些高级用法,比如委托构造函数或显式调用拷贝构造函数,你可能仍然需要使用传统的初始化语法。
Go语言的Defer
难以释放的资源与RAII
void f() {
int*p = new int{3};
int error = doSomething(p);
if (error)
return;
finalize(p);
delete p;
}
一开始申请了内存,存储int,在最后函数结束的时候,通过delete p释放内存。上面的代码就有可能内存泄漏。因为假设doSomething 返回了错误,函数就会提前结束,而delete p就不会执行导致内存泄漏了!
修改代码
void f() {
int*p = new int{3};
int error = doSomething(p);
if (error) {
delete p; //释放内存,当出现错误的时候
return;
}
finalize(p);
delete p;
}
是不是添加了上面的错误处理,就不会有内存泄漏了?不!上面的代码可能还存在内存泄漏。如果doSomething抛出异常,那么两个delete p都不会被执行,内存泄漏!
有人可能会说,事儿怎么这么多,直接加上try catch,在catch的时候释放内存不就OK了嘛?实际上并不OK,因为加上try catch犯的是更严重的错误。
而且就算加了,代码还是可能会内存泄漏。因为哪天有程序员(可能是自己)增加新的代码的时候,就可能忘记delete了。
所以这种靠程序员细致认真的方法,是不靠谱的!
C++提供的解决方案是RAII,RAII 全称就Resource acquisition is initialization. 意为资源获取要通过构造函数初始化,然后析构函数负责释放资源。RAII替我们实现了”某个操作在任何分支结束的时候,会被执行,且被执行一次“。
C++ 版本的Defer
在C++ 中叫做Scope Guard,配合lambda一起使用。
ScopeGuard保证函数结束时,不论怎么退出,都会delete当前的指针。ScopeGuard一般用于关闭资源。
#include<functional>
#include<iostream>
class ScopeGuard{
std::function<void()> mFunc;
public:
ScopeGuard(std::function<void()> f) {
mFunc = f;
}
~ScopeGuard() {
mFunc();
}
};
int doSomething(int* p);
void finalize(int* p);
void f() {
int* p = new int{3};
ScopeGuard scopeGuard ([&p]() { //这里使用了ScopeGuard
if (p)
delete p;
std::cout << "delete point\n";
});
int error = doSomething(p);
if (error)
return;
finalize(p);
std::cout<<"Function ends!\n";
}
int main(){
f();
}
int doSomething(int* p) {
return -1;
}
void finalize(int* p) {
}
C++ 标准库没有提供类似ScopeGuard的类,我们可以通过std::unique_ptr实现类似的行为。
void f()
{
int* p = new int{ 1 };
auto deferFun = [&p](void*) {
delete p;
};
std::unique_ptr<void, decltype(deferFun)> ptr1{ (void*)1, deferFun };
}
int main()
{
f();
return 0;
}
Go 语言的Channel
我们用生产者和消费者解释这个模型。
生产者向管道的一端放数据,消费者在管道另一端接收数据。
- 如果没有数据,消费者应该被阻塞。
- 如果有数据,应该通知消费者获取。
- 对于没有数据的情况,我们应该调用条件变量的wait方法,让消费者处于阻塞状态。
- 生产者放数据的时候,应该唤醒消费者,调用条件变量的notify_one方法。通知消费者获取
- 生产结束的时候,应该notify_all,唤醒所有阻塞的消费者。
///Go like Channel Implementation
template<class item>
class Channel {
private:
std::list<item> queue;
std::mutex m;
std::condition_variable cv;
bool closed;
public:
Channel() : closed(false) {}
void close() {
std::unique_lock<std::mutex> lock(m);
closed = true;
cv.notify_all();
}
bool is_closed() {
std::unique_lock<std::mutex> lock(m);
return closed;
}
void put(const item &i) {
std::unique_lock<std::mutex> lock(m);
if (closed)
throw std::logic_error("put to closed channel");
queue.push_back(i);
cv.notify_one();
}
bool get(item &out, bool wait = true) {
std::unique_lock<std::mutex> lock(m);
if (wait)
{
//这里为了防止虚假唤醒,添加了一个匿名函数
//当被虚假唤醒,队列为空,还会继续等待
cv.wait(lock, [&]() { return closed || !queue.empty(); });
}
if (queue.empty())
return false;
out = queue.front();
queue.pop_front();
return true;
}
};
Channel<bool> touchChannel;
void eventReaderThread(int fd) {
while(true)
{
touchChannel.put(true);
sleep(500);
}
}
void eventWriterThread(){
bool sync;
while(touchChannel.get(sync)){
//do something
}
}
把参数类型放到函数后面
C++ 的函数尾返回类型
auto function(int param) -> double; // 尾返回类型
auto function(int param) -> double; // 尾返回类型
//返回一个函数指针, 这个函数指针参数为空,返回类型为函数指针,该函数返回int
auto pf1()->auto (*)()->int(*)()
{
}
//如果不用追踪返回类型的写法, 绕来绕去~
int (*(*pdf())())()
{
}