写时复制技术,其思想为: 1.shared_ptr 是引用计数的智能指针,如果当前只有一个观察者,那么引用计数为1 2.对于write端,如果发现引用计数为1,就可以安全的修改共享对象,不用担心有人在读;如果引用计数大于1就复制一份数据在副本上进行修改 3.对于read端,读之前将引用计数加1,读完后减1,保证在读期间引用计数的值大于1,可以阻止并发写
#if 1 #include <iostream> #include <mutex> #include <thread> #include <vector> // #include <boost/shared_ptr.hpp> #include <stdio.h> using namespace std; class Foo { public: void doit() const; }; typedef std::vector<Foo> FooList; typedef std::shared_ptr<FooList> FooListPtr; FooListPtr g_foos; std::mutex mx; void post(const Foo& f) { printf("post\n"); std::lock_guard<std::mutex> lock(mx); if (!g_foos.unique())//写时,发现有人在共享数据就复制一份 { //g_foos不是唯一指向其对象的shared_ptr所以reset不会释放内存,所以traverse()中的foos还指向原来的内存还可以正常遍历 g_foos.reset(new FooList(*g_foos)); printf("copy the whole list\n"); } // assert(g_foos.unique()); cout <<"g_foos.unique() = " <<g_foos.unique() << endl; g_foos->push_back(f); } void traverse() { //读之前引用计数+1 (写实拷贝技术借助智能指针在不同对象之间共享内存。) FooListPtr foos; { std::lock_guard<std::mutex> lock(mx); foos = g_foos; cout <<"g_foos.unique() = " <<g_foos.unique() << endl; } // assert(!foos.unique()); this may not hold cout << " temp foos size = "<<(*foos).size() << endl; for (std::vector<Foo>::const_iterator it = foos->begin(); it != foos->end(); ++it) { cout << "[in for loop] temp foos size = "<<(*foos).size() << endl; it->doit(); } //函数退出时,局部变量销毁,引用计数减1 } void Foo::doit() const { Foo f; post(f); } int main() { g_foos.reset(new FooList); Foo f1,f2,f3; post(f1); post(f2); cout << "first ---------------------------> "<<endl; traverse(); post(f3); cout << "second ---------------------------> "<<endl; traverse(); }scoped_ptr 有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结
当最后一个指向x的shared_ptr离开其作用域时,x会同时在同一个线程中析构,但这个线程不一定是对象诞生的线程,如果对象的析构比较耗时,可能会拖慢关键线程的速度(如果最后一个shared_ptr引发的析构在关键线程中). 一般这种情况的优化办法为:用一个单独的线程专门做析构,通过BlockingQueue<shared_ptr>把对象的析构都转移(copy)到这个线程中.(应该是在这个析构线程中通过reset方法来析构)
shared_ptr可以持有任何对象,而且能安全的释放
通俗概括下即: 1释放错了,2忘记释放了,3多次释放了,4释放过了还去用,5频繁申请又释放,6越界了
避免循环引用的方法: owner持有指向child(owner 的成员)的shared_ptr,child(owner的成员)持有指向owner的weak_ptr
1.shared_from_this()在对象的内部获取自己的 shared_ptr 2.shared_from_this()不能在构造函数中调用,因为构造函数中它自身还没有被shared_ptr接管 3.要想使用shared_from_this().那么类必须继承enable_shared_from_this
使用示例:
new做了三件事 1)调用operator new分配内存 ;2)调用构造函数在内存上生成类对象;3)返回相应指针。 delete 1)析构对象;2)释放内存
new把内存分配合构造对象这两件事合在一起了,所以不够灵活,如果想只分配内存,可参考allocator类及相应方法。不怎么常用,暂不展开。C++ primer P427
Demo 1.注意shared_ptr与unique_ptr在处理动态数组时的区别
//1.在声明时的区别 unique_ptr<int[]> up(new int[10]());//正确 //shared_ptr<int[]> sp(new int[10]());//非法的 shared_ptr<int> sp(new int[10]());//和法的,但不正确,正确的方法还要传递自定义的删除器 shared_ptr<int> sp(new int[10](), [](int *p){delete[] p; });//和法的,且正确 //2.在赋值时的区别 for (size_t i = 0; i < 10; i++) { up[i] = i; //sp[i] = i ;//非法的 shared未定义下标运算符 *(sp.get() + i) = i; }以sp为例观察下内存,up类似
当new分配一个数组时,得到的并不是一个数组类型的对象,而只是数组元素类型的指针(第一个元素地址的指针)
//6,new 与数组 //方法1 可以看出size不必是常量 int size = 10; int *ptr1 = new int[size];//为初始化 //对于string类型不管有没有()都是初始化为空的 int *ptr2 = new int[size]();//初始化为0 ptr1[3] = 10; ptr2[3] = 10; //方法2 这里必须是常量了 typedef int arr[10]; int *ptr3 = new arr;//为初始化 int *ptr4 = new arr();//初始化为0ptr1没有初始化并赋值: ptr2初始化并赋值:
1.shared_ptr伴侣(使用时必须用shared_ptr初始化),弱引用不增加引用计数。 2.指向的内存可能不存在,必须用lock方法转化为shared_ptr
原则概括: 1.unique_ptr必须用动态内存初始化,否则虽然可以编译通过(合法),但生命周期结束时会异常(不正确) 2.unique_ptr也不要和内置指针混用,否则可能多次释放同一内存 3.shared_ptr与之类似。
//5.合法与不合法形式 { int ix = 1024; int *pi = &ix; int *pi2 = new int(2048); //1.不合法,ix是实例不是指针 //unique_ptr<int> p0(ix); //2.合法,但错误,因为pi指向静态内存,离开作用域后,p1销毁会引发异常 //unique_ptr<int> p1(pi); //3.合法,正确 unique_ptr<int> p2(pi2); //4.合法,但错误,因为pi指向静态内存,离开作用域后,p3销毁会引发异常 //unique_ptr<int> p3(&ix); //5.合法,正确 unique_ptr<int> p4(new int(1024)); //6.合法 ,但错误。因为这样相当于混合使用了智能指针和内置指针(get返回的),p5和p2各自独立创建 //指向同一内存,离开作用域时释放了两次 unique_ptr<int> p5(p2.get()); } system("pause");unique_ptr没有类似make_shared的初始化方式 shared_ptr和unique_ptr的reset功能类似。
#include<iostream> #include<vector> #include<memory> #include<string> using namespace std; //可以拷贝一个将要被销毁的unique_ptr unique_ptr<string> clone1(string a){ return unique_ptr<string>(new string(a)); } //可以拷贝一个将要被销毁的unique_ptr unique_ptr<string> clone2(){ //可以返回一个局部对象的拷贝 unique_ptr<string> ret(new string("haha")); return ret; } int main() { unique_ptr<string> p1(new string("string")); //1.unique_ptr不支持拷贝 //unique_ptr<string> p2(p1); //unique_ptr<string> p3 = p2; //1.1不能拷贝unique_ptr的例外情况是:可以拷贝或赋值一个将要被销毁的unique_ptr unique_ptr<string> p1_0 = clone1("123"); cout << *p1_0 << endl;//123 unique_ptr<string> p1_1 = clone2(); cout << *p1_1 << endl;//haha //2.通过release()放弃指针的控制权,返回指针。并将原指针p1置空 unique_ptr<string> p4(p1.release());//将p1转移给p4 cout << "p4 = " << *p4 << endl; //p1被置空(p1并没有被释放,而是转移给了p4),对空指针解引用已发异常 //cout << "p1 = " << *p1 << endl; //3.使用reset释放原来的内存并将指针指向新的内存 unique_ptr<string> p5(new string("test")); p4.reset(p5.release());//test cout << "p4 = " << *p4 << endl; //cout << "p5 = " << *p5 << endl;//p5被置空(p5并没有被释放,而是转移给了p4),对空指针解引用已发异常 //4.release引起的内存泄漏 //p4.release();//(A)p4只是被置空了,p4指向的内存并没有被释放,如果没有另外的指针接收,内存就会泄漏 cout << p4.get() << endl;//返回 0000000;因为p4被置空了 auto p6 = p4.release();//p6=NULL,因为之前已经release一次了; //如果(A)处没有release,p6拿到了内存,后面就要用delete(p6)来手动管理内存了 system("pause"); return 0; }用法1:
shared_ptr<int> p; p = new int(1024);//错误,不允许这样, p.reset(new int(1024));//正确用法2:
reset成员经常和unique一起使用,来控制多个shared_ptr共享对象(共享,参考1),用来在改变共享对象之前判断是不是当前对象仅有的用户。如果不是,有不想共享,可以先拷贝一份
if(!p.unique()){ p.reset(new string(*p))//不是唯一用户,分配新的拷贝 } *p+=newval;//是唯一了,可以改变对象的值了说明同5。根本原因在于多个指针指向同一个内存,且多个指针都可能随时随地释放掉内存
int *x(new int(4)); { shared_ptr<int> ptr = shared_ptr<int>(x); }//脱离作用域后内存被智能指针释放了 int y = *x; cout << y << endl;//-17891602(未定义) system("pause"); return 0;说明,demo参考5
get函数的使用场景是:需要向不能使用智能指针的代码传递一个内置指针。只有在确定get拿的的指针不会被delete的情况下才可以使用get。特别是:永远不要用get初始化另一个智能指针或者个另一个智能指针赋值。根本原因在于这样创建的智能指针是相互独立创建的,各自有各自的引用计数,但却指向同一块内存。这就容易造成,当一个地方使用时另一个地方可能已经释放了该内存。类似的一旦将一个普通(内置)指针交给shared_ptr管理后就不要使用内置指针访问shared_ptr指向的内存了。
#if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vector> #include<memory> #include<string> using namespace std; int main() { shared_ptr<int> p(new int(42)); int *q = p.get();//(1) cout << "(1)p.value = " << *p << endl; cout << "(1)p.count = " << p.use_count() << endl; { //int *q = p.get();//如果把(1)移到这里,不调用delete并不会有问题,离开作用域后q指针虽然不存在了,但是指向的内存并没有被释放 //delete q; //qq和p指向了相同的内存,因为它们是各自独立创建的,所以应用计数均为1 shared_ptr<int> qq = shared_ptr<int>(q); cout << "qq.count = " << qq.use_count() << endl;//1 }//程序结束,qq指向的内存被释放,导致p指向的内存也被释放,但下面的测试可以看出p的引用计数还没有被释放 cout << "(2)p.count = " << p.use_count() << endl;//1 int foo = *p; cout << "(2)p.value = " << *p << endl;//未定义的值,p指向的内存已经被释放了 system("pause"); return 0; } /* (1)p.value = 42 (1)p.count = 1 qq.count = 1 (2)p.count = 1 (2)p.value = -17891602 请按任意键继续. . . */ #endif下面代码从buffer1中分配空间给结构chaff,从buffer2中分配空间给一个包含20个元素的int数组。 定位new运算符的另一种用法是:将其与初始化结合使用,从而将信息放在特定的硬件地址处。基本上,它只是返回传递给它的地址,并将其强制转换为void *,以便能够赋给任何指针类型。
#include <new>//必须包含 char buffer1[50]; char buffer2[500]; struct chaff { char dross[20]; int slag; }; chaff *p1, *p2; int *p3, *p4; p1=new chaff; //place structure in heap p3=new int[20]; //place int array in heap p2=new (buffer1) chaff; //place structure in buffer1 p4=new (buffer2) int[20]; //place int array in buffer2说明:同2
int *ptr = new int(100); shared_ptr<int> p1(ptr); { shared_ptr<int> p2(ptr); } cout << *p1 << endl;//-17891602常用方法:
智能指针可以通过引用计数来协调对象的析构,但仅限于自身的拷贝(即share_ptr)之间。所以推荐使用make_shared来初始化,尽量不用new,因为可能将同一块内存独立绑定到了多个shared_ptr上。
#if 1 #include <iostream> #include <memory> #include <string> #include <functional> using namespace std; class CSmartPointer{ public: CSmartPointer(){ } CSmartPointer(int data):n_data_(data){ } CSmartPointer(int data,string str) :n_data_(data),str_data_(str){ } CSmartPointer(std::function<void(int)> f){ n_data_ = 10; func = f; /*func(n_data_);*/ } public: int n_data_; string str_data_; std::function<void(int)> func; }; void test(int a) { cout << a << endl; } int main() { //1.智能指针初始化 std::shared_ptr<CSmartPointer> p1; p1 = shared_ptr<CSmartPointer>(new CSmartPointer(1)); std::shared_ptr<CSmartPointer> p2; p2 = shared_ptr<CSmartPointer>(new CSmartPointer(2,"test")); //2.通过make_shared初始化 std::shared_ptr<CSmartPointer> p3; p3 = make_shared<CSmartPointer>(2); std::shared_ptr<CSmartPointer> p4; p4 = make_shared<CSmartPointer>(2,"test");//括弧中的是CSmartPointer的参数,这点老是忘记 //3.不允许这样初始化 //std::shared_ptr<CSmartPointer> p5; //p5 = new CSmartPointer(1,"error"); cout << p1->n_data_ << " " << p1->str_data_ << endl; cout << p2->n_data_ << " " << p2->str_data_ << endl; cout << p3->n_data_ << " " << p3->str_data_ << endl; cout << p4->n_data_ << " " << p4->str_data_ << endl; std::shared_ptr<CSmartPointer> p5; p5 = make_shared<CSmartPointer>(&test); p5->func(13); system("pause"); return 0; } #endif共享内存 这块一直忽视了,普通new指针也可以实现,只是需要手动管理内存。实际上利用的就是浅拷贝
利用智能指针实现共享内存,利用的就是浅拷贝,如果每次都深拷贝重新分配内存就不会达到共享内存的效果了 浅拷贝默认只复制了指针,导致多个指针指向同一块内存,如果仅使用裸指针会有一块内存被多次释放(或者当要去使用时其他地方已经释放)的问题,用智能指针代替裸指针后,深浅拷贝的问题依然存在,但不会像裸指针那样一个变量销毁导致内存被释放,反倒是巧妙实现了 共享内存的效果。
更优雅的一个例子:
class People{ private: string name_; std::shared_ptr<int> money_ ; std::shared_ptr<vector<string>> txt; public: People(){} People(string name, int money){ name_ = name; money_.reset(new int(money)); } void PrintMoney() { cout << name_ << " has Money = " << *money_ <<", pointer["<<money_<<"]"<< endl; } void SetMoney(int money){ //money.reset(new int(money));//如果这样写就不会有共享数据了(深拷贝),所以自己的实现的类中如果动态分配了内存,(如果要支持赋值)就应该重载赋值运算符实现深拷贝 *money_ = money; } void SetName(const string& name){ name_ = name; } void SetTxT(std::initializer_list<std::string> il){ txt = make_shared<vector<string>>(il); } void Insert(const string & t){ if(txt){ txt->emplace_back(t); } } void DumpTxT(){ cout<<"name:"<<name_<<" "; if(txt){ for(auto item : *txt){ cout<< " "<< item<<" "; } } cout <<"["<<txt<<"]"<< endl; } }; int main() { #if 0 People p1("p1",100); p1.PrintMoney();//p1 has Money = 100, pointer[0x606040] { People p2 = p1; p2.SetName("p2"); p2.SetMoney(800); p2.PrintMoney();//p2 has Money = 800, pointer[0x606040] p1.PrintMoney();//p1 has Money = 800, pointer[0x606040] p1.SetMoney(1000); p2.PrintMoney();//p2 has Money = 1000, pointer[0x606040] p1.PrintMoney();//p1 has Money = 1000, pointer[0x606040] } p1.PrintMoney();//p1 has Money = 1000, pointer[0x606040] /* //可以看到指针是一样的(浅拷贝) p1 has Money = 100, pointer[0x606040] p2 has Money = 800, pointer[0x606040] p1 has Money = 800, pointer[0x606040] p2 has Money = 1000, pointer[0x606040] p1 has Money = 1000, pointer[0x606040] p1 has Money = 1000, pointer[0x606040] */ #endif #if 1 People p1; { People p2; p2.SetName("P2"); p2.SetTxT({"111","222"}); p1 = p2; p1.SetName("P1");//如果p1调用了SetTxT也就不会出现共享内存的效果了 p2.DumpTxT();//name:P2 111 222 [0x60d0b8] p1.DumpTxT();//name:P2 111 222 [0x60d0b8] p1.Insert("P1++"); p2.Insert("P2++"); p1.DumpTxT();//name:P1 111 222 P1++ P2++ [0x60d0b8] p2.DumpTxT();//name:P1 111 222 P1++ P2++ [0x60d0b8] } //可以看到指针是一样的(浅拷贝) p1.DumpTxT();//name:P1 111 222 P1++ P2++ [0x60d0b8] /* name:P2 111 222 [0x60d0b8] name:P1 111 222 [0x60d0b8] name:P1 111 222 P1++ P2++ [0x60d0b8] name:P2 111 222 P1++ P2++ [0x60d0b8] name:P1 111 222 P1++ P2++ [0x60d0b8] */ #endif return 0; } #if 1 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<vector> #include<memory> #include<string> using namespace std; class StrBlob{ public: /*StrBlob(){ svv = new vector<string>(); }*/ public: void SetV(const vector<string>& v){ v_ = v; } void SetSV(std::initializer_list<std::string> il){ sv = make_shared<vector<string>>(il); } void SetSVV(std::initializer_list<std::string> il){ svv = new vector<string>(il); } void printV(){ for (auto item : v_){ cout << item << " "; } cout << endl; } void printSV(){ cout << "print shared pvector: "; for (auto item : *sv){ cout << item << " "; } cout << endl; } void printSVV(){ cout << "print common pvector: "; for (auto item : *svv){ cout << item << " "; } cout << endl; } void push_back(const string& str){ v_.push_back(str); } void push_back_sv(const string& str){ sv->push_back(str); } void push_back_svv(const string& str){ svv->push_back(str); } //void operator=(const StrBlob& anther){ // if (this == &anther){ // return; // } // this->v_ = anther.v_; // this->sv = anther.sv; //} private: vector<string> v_;//多个对象时彼此拥有的相互独立的数据 //(因为sv是指针啊,当不同的对象相互拷贝时,如果使用默认的拷贝构造那么仅仅只是浅拷贝, //仅拷贝了指针,所以肯定是共享的啊,可以通过深拷贝来让不同对象相互拷贝时拥有独立的数据) ==括号中理解是对的,利用智能指针实现共享内存,利用的就是浅拷贝,如果每次都深拷贝重新分配内存就不会达到共享内存的效果了== //默认只复制了指针,导致多个指针指向同一块内存进而出现一块内存被多次释放(或者当要去使用时其他地方已经释放)的问题 //用智能指针代替裸指针后,深浅拷贝的问题依然存在,但不会像裸指针那样一个变量销毁导致内存被释放 std::shared_ptr<vector<string>> sv;//多个对象时共享数据 vector<string>* svv; }; int main() { #if 0 //b1和b2独立拥有一份自己的低层数据 StrBlob b1; { vector<string> v1 = { "abc", "123", "hahaha" }; StrBlob b2; b2.SetV(v1); cout << "print b2:" << endl; b2.printV(); b1 = b2; cout << "print b1:" << endl; b1.printV(); //b1和b2是完全独立的 b1.push_back("hihao"); b1.printV(); b2.push_back("heihei"); b2.printV(); } cout << "print b1:" << endl;//b2销毁了,但b2中的vector完全拷贝给了b1;b1和b2是完全独立的 b1.printV(); /* print b2: abc 123 hahaha print b1: abc 123 hahaha abc 123 hahaha hihao abc 123 hahaha heihei print b1: abc 123 hahaha hihao 请按任意键继续. . . */ #endif #if 0//sb1和sb2共享一份低层数据 StrBlob sb1; { StrBlob sb2; sb2.SetSV({ "123", "qqq" }); sb2.printSV(); sb1 = sb2; sb1.push_back_sv("nihao"); sb2.push_back_sv("heihei"); //sb1和sb2共享一份数据 sb1.printSV(); sb2.printSV(); } sb1.printSV(); /* print shared vector: 123 qqq print shared vector: 123 qqq nihao heihei print shared vector: 123 qqq nihao heihei print shared vector: 123 qqq nihao heihei 请按任意键继续. . . */ #endif #if 1//sb1和sb2共享一份低层数据(使用普通动态指针,但这样需要手动管理内存) StrBlob sb1; { StrBlob sb2; sb2.SetSVV({ "123", "qqq" }); sb2.printSVV(); sb1 = sb2; sb1.push_back_svv("nihao"); sb2.push_back_svv("heihei"); //sb1和sb2共享一份数据 sb1.printSVV(); sb2.printSVV(); } sb1.printSVV(); /* print common pvector: 123 qqq print common pvector: 123 qqq nihao heihei print common pvector: 123 qqq nihao heihei print common pvector: 123 qqq nihao heihei 请按任意键继续. . . */ #endif system("pause"); return 0; } #endif