RAII 是 Resource Acquisition Is Initialization(资源获取即初始化)的缩写,是一种 C++编程技巧。RAII 技巧基于一个很简单的理念:在对象的构造函数中分配资源,而在析构函数中释放资源。这个理念看起来简单,但却具有强大的功能和好处。
最为代表性的就是 C++中的智能指针。
#include <iostream>
using namespace std;
class RAII
{
public:
char *buffer;
public:
();
RAII~RAII();
};
::RAII()
RAII{
= new char[1024];
buffer << "RAII::RAII" << endl;
cout }
::~RAII()
RAII{
delete buffer;
<< "RAII::~RAII" << endl;
cout }
int main(int argc, char **argv)
{
;
RAII raiireturn 0;
}
// RAII::RAII
// RAII::~RAII
写
C++都知道,如果我们封装一个类,然后封装为库给第三方调用,同时需要提供头文件,但是类中有许多成员变量暴露了太多细节,这一问题有没有办法处理呢
Pimpl(Pointer to implementation)是
C++编程中的一种惯用法,也称为“编译期实现”。它通过将类的实现细节从公共接口中分离出来,从而使类的实现变得更加抽象,提高了代码的可维护性、可扩展性和安全性。
//main.cpp
#include <iostream>
#include <memory>
#include "main2.h"
using namespace std;
int main(int argc, char **argv)
{
<Person> ptr = make_shared<Person>();
shared_ptr->print(); // 1 b 2 3
ptrreturn 0;
}
//main2.h
#include <memory>
class Person
{
public:
();
Person~Person();
void print();
private:
class Impl; // 内部类
std::unique_ptr<Impl> m_pImpl;
};
//main2.cpp
#include "main2.h"
#include <iostream>
using std::cout;
using std::endl;
class Person::Impl
{
public:
();
Impl~Impl() = default;
int a;
char b;
float c;
double d;
};
::Impl::Impl() : a(1), b('b'), c(2.0), d(3.0)
Person{
}
::Person()
Person{
m_pImpl.reset(new Impl());
}
::~Person()
Person{
}
void Person::print()
{
<< m_pImpl->a << " " << m_pImpl->b << " " << m_pImpl->c << " " << m_pImpl->d << endl;
cout }
//g++
++ -g -o test test.cpp -std=c++11
g//makefile
="-g -O0 -std=c++11"
make CXXFLAGS//cmake
(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g -Wall -O0 -Wno-unused-variable")
set//-Wno-unused-variable 表示禁止编译器对未使用的变量发出警告。
#include <iostream>
#include <string>
using namespace std;
// 需要写在声明中,在头文件内
class Person1
{
public:
int arr[5] = {1, 2, 3, 4, 5};
int number{999};
{"hello world"}; // c++11支持用花括号对任意类型的变量初始化
string str};
// 写到cpp文件中
class Person2
{
public:
() : arr{1, 2, 3, 4, 5}
Person2{
}
int arr[5];
};
// 写到cpp文件中
class Person3
{
public:
()
Person3{
[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;
arr}
int arr[5];
};
int main(int argc, char **argv)
{
;
Person1 person1<< person1.arr[0] << " " << person1.arr[4] << person1.number << " " << person1.str << endl; // 1 5 9999 hello world
cout ;
Person2 person2<< person2.arr[0] << " " << person2.arr[4] << endl; // 1 5
cout ;
Person3 person3<< person3.arr[0] << " " << person3.arr[4] << endl; // 1 5
cout {"hello"};
string strint number{1};
<< str << " " << number << endl; // hello 1
cout return 0;
}
详细内容可以看本笔记的 c++部分
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
class Person
{
public:
(initializer_list<int> m_list);
Person};
::Person(initializer_list<int> m_list)
Person{
for (const int &n : m_list)
{
<< n << endl;
cout }
}
int main(int argc, char **argv)
{
{1, 2, 3, 4, 5}; // 1 2 3 4 5
Person personreturn 0;
}
注解标签语法,C++11 支持任意类型、函数或 enumeration,从 c++17 支持了命名空间、enumerator
[[attribute]] types/functions/enums/etc
常见
[[noreturn]]:指定函数不会返回,可以用于提示编译器在函数返回之前不必生成清理代码。
[[nodiscard]]:指定函数的返回值不应该被忽略。
[[deprecated("reason")]]:指定程序实体已经被弃用,并且提供了一个理由。
[[maybe_unused]]:指定程序实体可能未被使用,用于消除编译器的“未使用变量”的警告。
[[fallthrough]]:指定在 switch 语句中,如果一个 case 语句中没有 break 语句,允许掉落到下一个 case 语句中。
[[nodiscard("reason")]]:C++20 引入的,用于给 [[nodiscard]] 添加一个理由。
样例
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
[[nodiscard]] int fun()
{
return 1;
}
int main(int argc, char **argv)
{
();
funreturn 0;
}
/*警告信息
note: declared here
6 | [[nodiscard]] int fun()
*/
C++98/03 enumeration
C++11 enumerator
#include <iostream>
#include <string>
#include <initializer_list>
using namespace std;
// 不限定作用域的枚举,外部可以访问
enum
{
,
RED,
BLACK
BLUE};
enum
{
,
ORANGE
DARK};
enum Type
{
,
TEACHER
STUDENT};
// c++11 enumberator
enum class Person
{
= 0,
men
women};
int main(int argc, char **argv)
{
int n1 = RED;
<< n1 << endl; // 0
cout << ORANGE << endl; // 0
cout = TEACHER;
n1 // n1 = Person::men;//错误
// cout << Person::men << endl;//错误
= Person::men; // 正确
Person person // Person person1 = 0; // 错误
// cout << person << endl;//错误
<< (person == Person::men) << endl; // 1
cout int men = 0; // 合法作用域,不与枚举冲突
return 0;
}
1、final
final 关键字修饰一个类,不允许被继承
class A final{
};
class B:public A;//error
2、override
在父类中加了 virtual 关键字的方法可以被子类重写,子类重写该方法时可以加或者不加 virtual,默认为 virtual 的
可能存在两种问题
#include <iostream>
using namespace std;
// 抽象类型A
class A
{
public:
virtual void run(int n) = 0;
virtual void fun(double n);
};
void A::fun(double n)
{
}
// 必须实现void run(int n) 不然B也是抽象类
class B : public A
{
public:
void run(int n) {}
// void fun(float n) // 其实没有成功重写void func(double n),编译不会报错
// {
// }
void fun(float n) override // 会报错
{
}
};
int main(int argc, char **argv)
{
;
B breturn 0;
}
3、=default
如果一个 C++类没有显式给出构造函数、析构函数、拷贝构造函数、operator=这几个类函数的实现,则在需要时编译器会自动生成。但是在给出这些函数的声明时却没有给实现,则会在编译器链接时报错,如果用了=default 标记编译器会给出默认实现
class Person{
public:
(int n);
Person()=default;
Person};
//只在cpp实现Person(int n) 即可
4、delete
禁止编译器生成构造函数、析构函数、拷贝构造函数、operator=
#include <iostream>
using namespace std;
class Person
{
public:
() = default;
Person~Person() = default;
&operator=(const Person &o) = delete;
Person (const Person &p) = delete;
Person};
int main(int argc, char **argv)
{
;
Person person1// Person person2 = person1; // 错误
;
Person person3// person1 = person3;//错误
return 0;
}
其实还有些骚操作,对于函数的重载而言
#include <iostream>
using namespace std;
void func(double f)
{
}
void func(float f) = delete;
void func(int f) = delete;
int main(int argc, char **argv)
{
(21.0); // 正确
func(12.3f); // 报错
func(12); // 报错
funcreturn 0;
}
用于编译时自动推导数据类型
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main(int argc, char** argv) {
auto str = "nvdfkv";//const char*
auto n1 = 12.0;//double
auto n2 = 12;//int
auto n3 = 13.3f;//float
auto str1 = string("dfvd");//string
<int> nums{ 1,2,3,4,5 };
vectorfor (auto iter = nums.begin(); iter != nums.end(); ++iter) {
<< *iter << endl;
cout }//iter=>std::vector<int>::iterator
return 0;
}
for-each语法遍历一个数组或集合中的元素
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main(int argc, char** argv) {
<int> nums{ 1,2,3,4,5 };
vectorfor (int n : nums) {//发生拷贝构造
<< n << endl;
cout }
for (const int& n : nums) {
<< n << endl;//引用
cout }
for (const auto& n : nums) {//const int&
<< n << endl;
cout }
return 0;
}
被迭代器的数据结构应该有begin与end方法,二者都返回迭代子,迭代子必须支持operator++、operator!=、operator*
#include<iostream>
#include<initializer_list>
using namespace std;
template<typename T,size_t N>
class A {
public:
(const initializer_list<T>&list) {
Asize_t i = 0;
for (auto& v : list) {
m_elements[i++] = v;
}
}
~A() {}
* begin() {
Treturn m_elements;
}
* end() {
Treturn m_elements + N;
}
private:
m_elements[N];
T };
int main(int argc, char** argv) {
<int, 10> a{1,2,3,4,5,6,7,8,9,10};
Afor (const int& n : a) {
<< n << endl;//1 2 3 4 5 6 7 8 9 10
cout }
return 0;
}
C++17 中引入了结构化绑定(Structured Bindings)的语法,允许我们将一个结构体或者 tuple 类型的对象解构为多个变量。结构化绑定语法的一般形式如下:
auto [var1, var2, ...] = expression;
auto [var1, var2, ...] { expression };
auto [var1, var2, ...] ( expression );
样例
#include<iostream>
#include<map>
#include<string>
#include<tuple>
using namespace std;
struct Point {
int x;
;
string y};
int main(int argc, char** argv) {
//结构体
;
Point point.x = 100;
point.y = "cpp17";
pointauto [x,y] = point;// int x,int y
<< x << " " << y << endl;//100 cpp17
cout auto& [x_ref, y_ref] = point;
= "point";
y_ref << point.y << endl;//point
cout //tupe
<int, double>mTupe{ 1,6.66 };
tupleauto [width, height] = mTupe;
<< width <<" "<< height << endl;//1 6.66
cout auto& [width_ref, height_ref] = mTupe;//支持const auto&等
= 666;
width_ref << get<0>(mTupe) << endl;//6666
cout //map
<string, int>mMap = { {"key1",1},{"key2",2}};
mapfor (auto& [key, value] : mMap) {//const std::string&key,int&value
<< key <<" "<< value << endl;//key1 1 key2 2
cout }
return 0;
}
结构化绑定限制
auto [first,second]=std::pair<int,int>(1,2);//正常
constexpr auto [first,second]=std::pair<int,int>(1,2);//无法编译
static auto [first,second]=std::pair<int,int>(1,2);//无法编译
有一点要注意的是,使用emplace系列函数,首先会创建对象t(调用一次构造函数),利用对象t拷贝构造函数构造出一个新对象放入集合中,t对象调用析构函数销毁
#include<iostream>
#include<map>
#include<string>
#include<list>
using namespace std;
struct Point {
int x;
;
string y(const int& x, const string& y) {
Pointthis->x = x;
this->y = y;
}
};
int main(int argc, char** argv) {
<Point>points;
list.emplace_back(1, "1");//后插 push_back
points.emplace_front(2, "2");//前插 push_front
points.emplace(points.begin(), 3, "3");//原位 push/insert
pointsfor (auto& v : points) {
<< v.x << " " << v.y << endl;
cout }
//3 3 2 2 1 1
return 0;
}
try_emplace:键已存在则不添加,不存在则构造添加
insert_or_assign:键存在则更新,键不存在则添加
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(int argc, char** argv) {
<string, int> mMap;
map auto res1=mMap.try_emplace("1",10 );//std::pair<iterator,bool> res1
if (res1.second) {//添加成功
<< res1.first->first <<" "<< res1.first->second<< endl;//1 10
cout }
auto res2=mMap.insert_or_assign("1",11);
<< boolalpha<< res2.second << endl;//false,则为赋值成功
cout return 0;
}
auto_ptr 是 C++98 标准引入的一种智能指针,用于管理动态分配的对象。auto_ptr 对象的特点是在构造时接管一个指针,并在析构时自动释放指针所指向的对象,因此可以避免内存泄漏的问题。
然而,auto_ptr 在使用时存在一些问题,比如它不能正确处理数组对象(只能处理单个对象)、不能传递所有权等。为了解决这些问题,C++11 标准引入了新的智能指针类 unique_ptr 和 shared_ptr,它们分别提供了独占所有权和共享所有权的语义,能够更好地管理动态分配的对象。
由于 auto_ptr 存在的问题以及新智能指针类的引入,C++11 标准已经将 auto_ptr 标记为废弃的,建议使用 unique_ptr 或者 shared_ptr 来替代它。在 C++17 中,auto_ptr 已经被彻底移除,不能再使用了。
详细的还是去看C++部分吧
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main(int argc, char** argv) {
<int> ptr1 = make_shared<int>(1);
shared_ptrauto ptr2 = ptr1;
<< ptr2.use_count() << endl;//2
cout .reset();
ptr2<< ptr1.use_count() << endl;//1
cout int *realPtr=ptr1.get();
<< ptr1.use_count() << endl;//1
cout return 0;
}
详细的还是去看C++部分吧
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main(int argc, char** argv) {
<int> ptr;
unique_ptrint* n = new int{};
.reset(n);
ptrint*real_ptr=ptr.get();
.release();//取消接管n,但不释放内存,reset即取消接管也释放内存
ptr//禁止赋值构造与拷贝构造
//unique_ptr<int>ptr1 = ptr;
//unique_ptr<int>ptr2(ptr);
*n = 1000;
<< *n << endl;//1000
cout delete n;
return 0;
}
详细的还是去看C++部分吧,在shared_ptr的使用中可能会出现循环引用的情况,造成内存泄露
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class A;
class B {
public:
<A> sharedA2;
shared_ptr~B() {
<< "B free" << endl;
cout }
};
class A {
public:
<B> sharedB2;
shared_ptr~A() {
<< "A free" << endl;
cout }
};
int main(int argc, char** argv) {
<A> sharedA1 = make_shared<A>();
shared_ptr<B> sharedB1 = make_shared<B>();
shared_ptr->sharedB2 = sharedB1;
sharedA1->sharedA2 = sharedA1;
sharedB1//会看见完蛋了,两个析构函数都没有执行,如果A,B构造函数内申请了动态内存
//可能造成内存泄露
<< sharedA1.use_count() << endl;//2
cout << sharedB1.use_count() << endl;//2
cout //当sharedA1 sharedB1变量栈内存时,二者引用变为1
//A B类的对象为动态内存,并不会得到释放
return 0;
}
可以使用weak_ptr,其并不会增加引用数量,可以将上面class A、classB中成员任意一个改为weak_ptr就可解决
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main(int argc, char** argv) {
<int> ptr1 = make_shared<int>(999);
shared_ptr<int> ptr2;
weak_ptr{
= ptr1;
ptr2 << ptr1.use_count() << endl;//1
cout <int>ptr3 = ptr1;
shared_ptr<< ptr2.use_count() << endl;//2
cout }
<< ptr2.lock() << endl;//00000249452F45D0 alive
cout .reset();
ptr1<< ptr2.lock() << endl;//0000000000000000 dead
cout return 0;
}
修正循环引用
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class A;
class B {
public:
<A> sharedA2;
weak_ptr~B() {
<< "B free" << endl;
cout }
};
class A {
public:
<B> sharedB2;
weak_ptr~A() {
<< "A free" << endl;
cout }
};
int main(int argc, char** argv) {
<A> sharedA1 = make_shared<A>();
shared_ptr<B> sharedB1 = make_shared<B>();
shared_ptr->sharedB2 = sharedB1;
sharedA1->sharedA2 = sharedA1;
sharedB1//B free
//A free
return 0;
}
enable_shared_from_this提供了需要在类中的返回包裹当前对象this的一个共享指针对象给外部使用
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class A:public enable_shared_from_this<A> {
public:
() {
A<< "A build" << endl;
cout }
~A() {
<< "A free" << endl;
cout }
<A> getSelf(){
shared_ptrreturn shared_from_this();
//return shared_ptr<A>(this);//错误
}
};
int main(int argc, char** argv) {
<A> ptr1 = make_shared<A>();//A build
shared_ptr<A> ptr2 = ptr1;
shared_ptr<A> ptr3 = ptr1->getSelf();
shared_ptr<< ptr1.use_count() << " " << ptr2.use_count() <<" "<< ptr3.use_count() << endl;
cout //3 3 3
return 0;
//A free
}
不要这样使用,栈内存对象,调用getSelf()
int main(int argc, char** argv) {
;
A aauto ptr = a.getSelf();
return 0;//错误
}
不要这样使用,可能存在循环引用的情况
#include<iostream>
#include<memory>
#include<string>
using namespace std;
class A:public enable_shared_from_this<A> {
public:
() {
A<< "A build" << endl;
cout }
~A() {
<< "A free" << endl;
cout }
void fun(){
=shared_from_this();
ptr}
private:
<A> ptr;
shared_ptr};
int main(int argc, char** argv) {
//情况1
//A a;
//a.fun();//shared_ptr会尝试释放栈内存
//情况2
auto ptr = make_shared<A>();
->fun();//A build,不会析构,循环引用造成内存泄露
ptrreturn 0;//错误
}
一个unique_ptr的大小与裸指针的大小相同,shared_ptr的大小是unique_ptr的两倍
#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main(int argc, char** argv) {
<int>ptr1;
shared_ptr<string>ptr2;
shared_ptr.reset(new string());
ptr2<int>ptr3;
unique_ptr<int>ptr4;
weak_ptr<< sizeof(ptr1) << endl;//x64:16 x86:8
cout << sizeof(ptr2) << endl;//x64:16 x86:8
cout << sizeof(ptr3) << endl;//x64:8 x86:4
cout << sizeof(ptr4) << endl;//x64:16 x86:8
cout return 0;//错误
}
好习惯:
1、一旦使用了智能指针管理一个对象,就不该再用裸指针操作它
2、知道在哪些场合使用哪种类型的智能指针
3、避免操作某个引用资源已经释放的智能指针
4、作为类成员变量,应优先使用前置声明
//B.h
class A;//优先使用前置声明,而不是#include"A.h"
class B{
public:
;
A a}
下面同理
class A;//优先使用前置声明,而不是#include"A.h"
class B{
public:
std::shared_ptr<A> a;
}