基本概念:https://www.cnblogs.com/LuckCoder/p/8467656.html
class A {
public:
explicit A(int value) : m_value(value) {}
private:
int m_value;
};
static void newVersionConstruct() {
int avector[] = { 1, 2, 3 };
std::vector bv = { 1, 2, 3 };
A a(10);
A b{ 3 };
//bool类型转成int类型的精度没有损失。true会转化成0,false转成1.
//因为没有精度损失,所以这种转化是允许的
A c(true);
A d{ true };
//A e(1.1);
//A f{1.0};
}
//由于传入的参数类型是const引用,因此传入左值或者右值都是可以的
void printInfo(const int& a) {
std::cout << a << std::endl;
}
static void leftRefAndRightRef() {
int a = 10;
int& refA = a; //引用类似于别名
const int& constRefA = a; //const引用可以防止对原数据的修改
std::cout << " a " << a << " ref of a " << refA << " const ref a "
<< constRefA << std::endl;
// this is a error
//int& refB = 10;
const int& constRefB = 10; //const引用可以指向一个右值
printInfo(constRefB);
printInfo(10);
printInfo(a);
printInfo(refA);
std::cout << "different version const ref " << constRefB << std::endl;
auto&& rrA = 10; //右值引用
const int&& crrB = 20;
rrA = 30; //可以通过右值引用改变一个右值的大小
std::cout << " right ref of b " << rrA << " const right ref b "
<< crrB << std::endl;
int b = 20;
// 下面两句是错误的,编译器不支持隐式的将左值转成右值
//int&& rrB = b;
//const int&& crrB = b;
//显示的将左值转成右值
int&& newRRB = std::move(b);
const int&& cNewRRB = std::move(b);
//b的地址和newRRB的地址和cNewRRB的地址相同
std::cout << " b " << b << " right ref of b " << newRRB << " const right ref b "
<< cNewRRB << std::endl;
std::cout << "address " << &b << " ref " << &newRRB << " c ref " << &cNewRRB << std::endl;
}
引言
//getName和getName2两个函数都只返回一个指针,这种设计存在一定的的缺陷:
//用户不明确指针指向的内存是否属于自己管理的范围
//多线程的环境下,该函数返回的指针只能当场使用,因为其他线程可以修改valueGroup的值
const char* getName() {
static char valueGroup[1000];
// do some thing
return valueGroup;
}
//多线程下返回的指针所指的值可以一直用,但是要记得手动释放内存
const char* getName2() {
char* value = (char*)malloc(1000);
// do something
return value;
}
//下面的函数可以解决上面函数的问题:返回的指针所指的那块内存是否需要程序员去手动释放
char* getName(char* v, size_t bufferSize) {
// do something
return v;
}
对于堆上的内存,可以使用malloc和free或者new和delete进行控制。但是如果不注意,还是会发生内存泄漏。如下:
void badThing() {
//抛出异常
throw 1;
}
void versionOne() {
//void*
int* ageC = (int*)malloc(sizeof(int));
//判断是否生成成功
if (ageC) {}
free(agcC);
char* c = (char*)malloc(100);
free(c);
int* age = new int(25);
int* height = new int(160);
//当badThing抛出异常之后,即使下面写了delete,age和height所指向的内存依然泄漏了
badThing();
std::cout << "VersionOne: your age is " << *age << ", and your height is "
<< *height << std::endl;
// if forget, TOO BAD thing will happen
delete height;
delete age;
}
当函数badThing()抛出异常之后,即使下面写了delete,age和height所指向的内存依然泄漏了。
如果我们写了一个类,用来控制内存的产生和释放,如下,在多线程的情况下,还是会出现一些问题:
class SafeIntPointer {
public:
explicit SafeIntPointer(int v) : m_value(new int(v)), m_used(1) {}
~SafeIntPointer() {
//下面的三句代码存在问题:在多线程情况下,可能会出现
//多个线程同时满足条件if (m_used <= 0)的情况
//此时就会出现问题m_value所指向的资源会被重复释放
m_used--;
if (m_used <= 0)
delete m_value;
}
// copy
// operator =
int* get() { return m_value; }
private:
int m_used; //记录使用者人数,当该值为0时,释放资源
int* m_value;
};
为此,C++11推出用于管理内存的智能指针——shared_ptr
void versionTwo() {
std::shared_ptr age(new int(28));
std::shared_ptr height(new int(160));
std::cout << "VersionTwo: your age is " << *age << ", and your height is "
<< *height << std::endl;
// 不需要做任何额外的事情,内存会自动的释放掉
}
智能指针总体概述
class Object {
public:
Object(int id) : m_id(id) { std::cout << "init obj " << m_id << std::endl; }
~Object() { std::cout << "bye bye " << m_id << std::endl; }
int id() const { return m_id; }
private:
int m_id;
};
typedef std::shared_ptr
当存在彼此依赖关系的时候,两个类都在等待对方释放资源之后再释放资源。因此造成了内存泄漏。例子如下:
#include
#include
class Parent;
typedef std::shared_ptr ParentPtr;
class Child{
public:
ParentPtr father;
~Child();
};
typedef std::shared_ptr ChildPtr;
class Parent{
public:
ChildPtr son;
~Parent();
};
Child::~Child(){ std::cout<<"bye child\n"; }
Parent::~Parent(){ std::cout<<"bye parent\n"; }
void testParentAndChild(){
ParentPtr p(new Parent());
ChildPtr c(new Child());
p->son=c; // c.use_count() == 2 and p.use_count() == 1
c->father=p; // c.use_count() == 2 p.use_count() == 2
}
int main(){
testParentAndChild();
return 0;
}
形成如下结构:
编译后执行,发现并没有执行类child和parent的析构函数:
c的析构函数在等p的析构函数被调用之后才会调用,p的析构函数在等待c的析构函数被调研之后才会被调用。
如果存在彼此之间的循环引用关系,则shared_ptr不能释放掉彼此应该释放的资源。为了打破局限性,引入weak_ptr。
weak_ptr可以打破循环引用。weak_ptr可能管理了一份资源,当weak_ptr管理的资源被外部其他智能指针管理,则该资源是有效的,否则,weak_ptr管理的资源是无效的。
void sharedPtrWithWeakPtr() {
//typedef std::shared_ptr
我们使用weak_ptr的替换Child类里面的ParentPtr,从而打破了循环引用。当father所管理的资源不背shared_ptr所管理时,father所管理的资源是无效的。
#include
#include
class Parent;
typedef std::shared_ptr ParentPtr;
typedef std::weak_ptr WeakParentPtr;
class Child{
public:
//ParentPtr father;
WeakParentPtr father;
~Child();
Child();
};
typedef std::shared_ptr ChildPtr;
class Parent{
public:
ChildPtr son;
~Parent();
Parent();
};
Child::Child() { std::cout << "hello child\n";}
Parent::Parent() { std::cout << "hello parent\n";}
Child::~Child(){ std::cout<<"bye child\n"; }
Parent::~Parent(){ std::cout<<"bye parent\n"; }
void testParentAndChild(){
ParentPtr p(new Parent());
ChildPtr c(new Child());
p->son=c;
c->father=p;
}
int main(){
testParentAndChild();
return 0;
}
那么,如果把Parent类中的son也改成weak_ptr会怎么样呢?
#include
#include
#include
// auto_ptr
// shared_ptr
// enable_shared_from_this // CRTP
// weak_ptr
// unique_ptr
void sharedPtrNotice();
class Parent;
typedef std::shared_ptr ParentPtr;
typedef std::weak_ptr WeakParentPtr;
// public std::enable_shared_from_this歧义模板递归参数模式 CRTP
class Child : public std::enable_shared_from_this {
public:
WeakParentPtr father;
~Child();
Child();
void checkRelation();
};
typedef std::shared_ptr ChildPtr;
typedef std::weak_ptr WeakChildPtr;
class Parent : public std::enable_shared_from_this {
public:
WeakChildPtr son;
~Parent();
Parent();
void checkRelation();
};
void handleChildAndParentRef(const Parent& p, const Child& c) {
auto cp = c.father.lock();
auto pc = p.son.lock();
if(cp.get() == &p && pc.get() == &c) {
std::cout << "right relation\n";
} else {
std::cout << "oop!!!!!\n";
}
}
//传入const智能指针的引用是为了减少拷贝次数
void handleChildAndParent(const ParentPtr& p, const ChildPtr& c) {
//assert(c); 可以用于验证传入的是否有效
//如果传入的是空指针,则会抛出异常
//weakptr通过lock函数获得shared_ptr.
auto cp = c->father.lock();
auto pc = p->son.lock();
if(cp == p && pc == c) {
std::cout << "right relation\n";
} else {
std::cout << "oop!!!!!\n";
}
}
Child::Child() { std::cout << "hello child\n";}
Parent::Parent() { std::cout << "hello parent\n";}
Child::~Child() { std::cout << "bye child\n";}
Parent::~Parent() { std::cout << "bye parent\n";}
//调用handleChildAndParent
void Parent::checkRelation() {
//weakptr不能直接用,要通过lock函数验证一下
//如果son不是weakptr,则下面的过程可以改写成
//handleChildAndParent(Parent的智能指针,son);
auto ps = son.lock();
if(ps) {
// this
handleChildAndParent(shared_from_this(), ps);
//下面的写法是错误的,执行完之后,会调用一次析构函数。
//从而导致析构函数被调用两次
/*
ParentPtr p(this);
handleChildAndParent(p, ps);
*/
}
std::cout << "after call checkRelation\n";
}
void Child::checkRelation() {
// we call handleChildAndParent
}
class Object {
public:
Object(int id) : m_id(id) { std::cout << "init obj " << m_id << std::endl; }
~Object() { std::cout << "bye bye " << m_id << std::endl; }
int id() const { return m_id;}
private:
int m_id;
};
typedef std::unique_ptr UniqueObjectPtr;
void print(const UniqueObjectPtr& obj) {}
void transfer(UniqueObjectPtr obj) {
std::cout << obj->id() << std::endl;
}
void uniquePtr() {
UniqueObjectPtr obj(new Object(1));
auto p = obj.get(); // 获得指针的原生接口
//判断原生指针是否存在
if(p) {}
// better 也可以拿来直接用,判断原生指针存在不存在
if(obj) {}
// operator -> *
std::cout << p->id() << obj->id() << (*obj).id() << std::endl;
print(obj);
p = obj.release();
delete p;
//uniquePtr有两种reset形式,如下:
obj.reset();
obj.reset(new Object(2));
/* 上面这句等价于:
先把原有的资源释放掉obj.reset();
然后再去管理新的指针
*/
//在unique_ptr中
// UniqueObjectPtr(const UniqueObjectPtr&) 这种形式的拷贝构造函数是不存在的
// UniqueObjectPtr(UniqueObjectPtr&&) 存在这种形式的拷贝构造函数
//因此可以通过传入右值来
//transfer传入的是一个unique_ptr的“值”。
//由于unique_ptr的特性是同一时刻只能有一个unique_ptr来管理这份资源
//因此调用transfer时,可以传入一个右值引用,这样会调用unique_ptr的传入右值的拷贝构造函数
transfer(std::move(obj));
assert(obj == nullptr);
//std::cout << obj->id() << std::endl;
obj.reset(new Object(4));
//将unique_ptr转化成shared_ptr
ObjectPtr sharedObj(std::move(obj));
assert(obj == nullptr);
}
void sharedPtrNotice() {
// 前提:绝对不要自己手动的管理资源
/*因此下面的代码是不好的:
第一种形式:
int* a = new int(10);
delete a;
第二种形式:
int* b = malloc(sizeof(int));
if (b) {
free(b);
}
*/
//第一点:
//一个裸的指针不要用两个shared_ptr管理, unique_ptr也是一样。
//会出现资源的重复释放
auto pObj = new Object(1);
ObjectPtr obj(pObj);
ObjectPtr obj2(pObj);
//第二点:
// 用weak_ptr打破循环引用,parent 和 child
// 当需要在类的内部接口中,如果需要将this作为智能指针来使用的话,需要
// 用该类派生自enable_shared_from_this
//第三点:
// 使用shared_ptr作为函数的接口,如果有可能有const shared_ptr&的形式
// 多线程模式下使用shared_ptr需要注意的事项
//第四点:
// shared_ptr、 weak_ptr 和裸指针相比,空间上会大很多,并且效率上会有影响。
//尤其是在多线程模式下
//对于下面的过程,裸指针的操作是原子的。而智能指针需要先做两个引用计数加1以及一个资源的拷贝。
//在多线程情况下,为了保证拷贝是原子安全的,还要做很多额外的操作
ObjectPtr obj3(new Object(2));
ObjectPtr obj4 = obj3;
//第五点:
//可以通过如下方法来构造智能指针
ObjectPtr obj5 = std::make_shared(3);
// ObjectPtr obj5(new Object(3)); 这句和上面那句是等价的,但是上面那句的效率更好
//第六点:
//enable_shared_from_this中有一个函数——shared_from_this()(他不能在构造函数和析构函数中使用)和构造析构函数一样,
// 某些情况下,会出现内存不会降的问题。尤其是使用weak_ptr来处理循环引用的问题
//当某些资源已经失效了,但是weak_ptr在引用这个资源,因此该资源不能被释放,内存就因此提升了
//第七点:
// 如果有可能,优先使用类的实例,其次万不得已使用std::unique_ptr,
// 万不得已使用std::shared_ptr
Object obj6(6);
}