完美转发
std::forward()
这是一个模板概念,将模板中的万能引用 T&& a **(模板中的&&表示万能引用,既能接受左值,也能接受右值)**通过 T 还原为 a 原本的类型。
假定现在有某一对象 A 要通过模板函数 tf(参数为万能引用)以引用的形式传入函数 f 中,为了保证 A 传入 tf 的引用方式与 tf 中 A 传入 f 的引用类型相同需要,使用 std::forward 进行完美转发,具体看一下程序
template<typename T>
void emplace_back(T&& arg){
}
Class Base{
};
int main(){
Base a;
emplace_back(a); // ok
emplace_back(Base()); // also ok
return 0;
}
void RFn(int&& arg){
}
template<typename T>
void ProxyFn(T&& arg){
RFn(arg);
}
void main(){
ProxyFn(1);
//右值版本无法传递,int无法到 int&&,参数不匹配
//原因在于左值和右值引用在完成参数传递的时后,再使用时退化成了左值
}
引入了std::forward, 将模板函数改成如下形式就可以了, forward被称为完美转发
template<typename T>
void ProxyFn(T&& arg)
{
RFn(std::forward<T>(arg));
}
C++: 左值引用(&), 右值引用(&&),万能引用(template &&)详解 与 完美转发(forward) 实现剖析 - woder - 博客园 (cnblogs.com)
模型别名
using itType=std::vector<string>::iterator
=typedef std::vector<string>::iterator itType
差别,using可以用于模板部分具体化
template<typename T>
using arr12=std::array<T,12>
array<double,12>a1;
//=
arr12<double> a1;
p* base2= new p;
if(p *der2=dynamic_cast<p*>(base2))
der2->show();
else
cout<<"failure"<<endl;
C++11为何要引入右值引用?
首先说一下将亡值,就是在这一行结束之后该值将被析构。最典型的将亡值就是匿名对象(lambda)。
如果不想将亡值立刻析构,以前使用常引用将它的生命周期延长到和这个常引用相同的长度,
现在需要右值引用来分担const的工作,将那些产生的将亡值“偷过来”,延长它的生命周期,从而避免不必要的多次内存分配和销毁。
class Buffer{
public:
Buffer(){
buf=new int(100);
}
~Buffer(){
if(buf!=nullptr)
delete buf;
}
int* buf=nullptr;
};
Buffer getBuffer()
{
Buffer buf;
return buf;
}
void setBuffer(const Buffer &buf) {
cout<<"const buf"<<endl;
}
void setBuffer( Buffer && buf) {
cout<<"&& buf"<<endl;
}
void setBuffer( Buffer & buf) {
cout<<" buf"<<endl;
}
void testLvalue()
{
const Buffer & bufref1=getBuffer();
Buffer bufref;
setBuffer(bufref);//新构建了一个对象,带来开销
setBuffer(bufref1);
setBuffer(getBuffer());//
}
//output
buf
const buf
&& buf
【乔红】裤衩 C++ 之 右值引用(一)为什么会有右值引用_哔哩哔哩_bilibili
C++11新标准中const与constexpr的区别和联系
发生在运行阶段
发生在编译阶段,提升运行效率
| const | constexpr | |
| 修饰变量 | 表示一个变量无法改变,但初值不确定,不能在编译阶段确定 | 变量初值在编译阶段就确定,所以变量需要是一个常量表达式 |
| 修饰指针 | 分为两种情况 顶层const,指针变量无法修改, 如: int i=10; int *const p1=&i;//指针常量<br>p1=new int(2);//错误<br>底层 const,代表指针所指对象无法修改,如 :int const* p2=&i; //常量指针 *p2=3;//错误二者的区分关键在于指针是右结合的, | 仅仅对指针有效,与指针所致对象无关。int x=1; //x在函数体外 constexpr int* p=&x; *p=2; p=nullptr;// error无法修改 |
| 修饰函数 | 函数不会修改类的状态(即不会通过任何方式修改类数据成员)。其他可见上表 | 无法修饰成员函数,只能作为函数返回值类型,表明该函数返回的是一个编译期可确定的常量;constexpr 被隐式指定为内联函数,如constexpr int getMaxSize() { return INT_MAX; }<br>当函数体含有需要在运行时才能确定的量时错误 |
C++> const和constexpr区别与联系 - 明明1109 - 博客园 (cnblogs.com)
类型推导
auto
让编译器在编译阶段就能够判断变量的类型,不会影响程序的执行效率和编译效率。
如下所示
#include<iostream>
#include<string>
#include<memory>
#include<assert.h>
#include<functional>
#include<vector>
#include<algorithm>
using namespace std;
class Foo
{
public:
int val;
Foo(int x):val(x){}
private:
};
int main()
{
// auto a;c错误,auto需要通过初始化表达式进行类型推导。
// auto db=0.2,ib=2;//错误db与ib类型不一致
auto i=0;
const int ci=i,&cr=ci;
auto b=ci;//b是一个整数,auto会忽略变量的顶层const属性
//希望推断的是顶层const类型,需要标明, const auto b=ci
auto c=cr;//同理
auto d=&i;//d是一个整形指针
auto e=&ci;//e是一个指向整形常量
auto func=less<int>();
vector<int> v{4,3,4,5};
auto itb=v.begin();//相当于 vector<int>::iterator itb=v.begin();
auto ite=v.end();//简化模板声明
sort(itb,ite,func);
auto ptr=new Foo(2);
for(auto i:v)
cout<<i<<endl;
return 0;
}
decltype
T:从一个变量或表达式中得到类型
Y:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量
使用:
template<typename T>
int f(T &a)
{
T x=a*a;
return x;
}
void testDecltype()
{
int i=3;
decltype(i) y=i+3;
decltype(f(i)) s=y;//s为f函数返回的类型
const int cj=0,&ci=cj;
// decltype(ci) z;//error,z是一个引用必须初始化
//!!!decltype((variable))(注意是双层括号)的结果永远是引用,
// 而decltype(variable)结果只有当variable本身就是一个引用时才是引用。
// decltype((i)) e;//error,e是int&,必须初始化
}
sizeof(string)不同编译器结果不同
cout<<sizeof(s)/sizeof(string)<<" string mingw"<<endl;//1
cout<<sizeof(s)<<endl;//32,vs为28
cout<<sizeof(char)<<endl;//1
引用
左值右值
T
左值可以放到等号左边,可以取地址,并有名字
++i, 字符串"abcd"
右值不可以放在等号左边**,不能取地址,没有名字**
i++,1,2.2…
左值引用
对左值进行引用的类型,也就是对象的一个别名。
并不拥有所绑定对象的堆区内存,必须初始化,且等号右边的值必须可以取地址或者使用const引用形式。
int j=6;
const int k=3;
int &r=j*3;//error;j*3 is right value
//const引用
int &r1=k;//error,非const左值引用,编译失败
const int &r3=k;//const 左值引用,编译通过
右值引用
Y
以前的引用只能操作左值,而无法操作右值。
int x=0;
int &y=x;
int &z=0;//error
//但是允许常量左值引用操作右值
const int &c=10;
为了能够移动对象,而无需拷贝,提升了性能,C++11新标准支持右值引用,
标准库容器、string和shared_ptr类既支持移动也支持拷贝。IO类和unique_ptr类可以移动但不能拷贝
T
对右值进行引用的类型
W
int x=3;
int &&rr=42;//正确
int &&rr1=rr;//error, rr is left value
int &&rr2=x;//error,不能将一个右值引用绑定在左值上
const int &&rr3=rr;// error
使用std::move函数定义在头文件utility中,**能够强制把左值转换为右值。**通过移动构造函数使用移动语义,所谓移动语义,也就是转移所有权,对于那块资源转为自己所拥有的,别人不再拥有也不会使用。
注,移动语义针对的是那些实现移动构造函数的类 的对象,对于基本类型int,float没有优化作用,
int &&rr4=std::move(rr);
左值与右值引用的区别
当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的对象,即左值持久,右值短暂
智能指针
T
是行为类似于指针的类对象,将常规指针进行封装,当智能指针对象过期时,让它的析构函数对常规指针进行内存释放。
在类中需要去重载关于指针的运算符,如*,→,
代码如下:
#include <iostream>
using namespace std;
class Person {
public:
Person(int age) {
cout << "Person的构造函数" << endl;
m_Age = age;
}
// 输出成员的信息
void showAge() {
cout << "m_Age = " << this->m_Age << endl;
}
~Person(){
cout << "Person的析构函数" << endl;
}
int m_Age;
};
// 智能指针类
class SmartPointer {
public:
Person* operator->() {
return this->m_Person;
}
Person& operator*() {
return *m_Person;
}
SmartPointer(Person* p) {
m_Person = p;
}
Person* m_Person; // 内部维护的Person的指针
~SmartPointer()
{
cout << "SmartPointer析构函数" << endl;
if (this->m_Person != nullptr) {
delete this->m_Person;
this->m_Person = nullptr;
}
}
};
// 指针运算符重载 -> *
int main() {
// 创建Person的对象
Person* p = new Person(30);
p->showAge();
(*p).showAge();
// delete p;
cout << "==============" << endl;
SmartPointer sp(p);
sp->showAge(); // sp.operator->();
// sp->->showAge(); 编译器会优化成 sp->showAge();
(*sp).showAge();
//不用自己delete
return 0;
}
Y
避免忘记delete造成内存泄漏
#include<iostream>
#include<string>
using namespace std;
void snew(string &str)
{
string *ps=new string(str);
*ps+="gogo";
str=*ps;
return;
}
int main()
{
string s="c++11";
snew(s);
cout<<s;
return 0;
}
上面的程序存在一个问题,函数每次调用分配堆中的内存没有收回,从而导致内存泄漏,需要在snew中return前添加delete ps,但有时函数中存在打断函数执行的步骤,如异常、中断,导致没有delete操作,也会内存泄漏。
W
shared_ptr
在拷贝构造是使用同一份引用计数,共享同一个对象,对象的最末一个负责销毁对象,并清理与该对象相关的所有资源。
使用
share_ptr<string> p1;//指向string类型的空指针
*p1="hi";

使用模板手动实现
```C++
//模板类作为友元需要声明
template <typename T> class smartPtr;//note class
template <typename T> class refPtr{
private:
friend class smartPtr<T>;
//定义智能指针类为友元,because smart point need to operate refPtr
refPtr(T *ptr):p(ptr),count(1){}
//deconstruct
virtual ~refPtr(){
delete p;
}
//引用计数
int count;
//base object point
T *p;
};
template<typename T>class smartPtr
{
private:
refPtr<T> *rp;//辅助类对象指针
public:
smartPtr(T *ptr):rp(new refPtr<T>(ptr)){}
smartPtr(const smartPtr<T> &sp):rp(sp.rp)
{
++rp->count;
}
//overload
T* operator ->()
{
return rp->p;
}
T& operator *()
{
return *(rp->p);
}
smartPtr& operator=(const smartPtr<T>& rhs)
{
++rhs->count; //首先将右操作数引用计数加1
if(--rp->count==0)//然后将引用计数减1,可以应对自赋值
delete rp;
rp=rhs.rp;
return *this;
}
~smartPtr()
{
if(--rp->count==0)
delete rp;
else
cout<<" and the number of "<<rp->count<<" points point base object"<<endl;
}
};
void testsharePtr()
{
int *ia=new int(10);
{
smartPtr<int> sptr1(ia);
cout<<"sptr1:"<<*sptr1<<endl;
{
smartPtr<int> sptr2(sptr1);
cout<<"sptr2: "<<*sptr2<<endl;
*sptr2=5;
{
smartPtr<int> sptr3=sptr1;
cout<<"sptr3:"<<*sptr3<<endl;
}
}
}
}
测试结果
sptr1:10
sptr2: 10
sptr3:5
and the number of 2 points point base object
and the number of 1 points point base object
unique_ptr(C++11)开始引入
是一种在异常时可以帮助避免资源泄漏的智能指针。采用独占式拥有,意味着可以确保一个对象和其相应的资源同一时间只被一个 pointer 拥有。一旦拥有着被销毁,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,其任何相应资源亦会被释放。
两个 unique_ptr 实例之间的所有权转换。

基本操作:
// 智能指针的创建
unique_ptr<int> upi;//创建空智能指针
upi.reset(new int(3));//绑定动态对象
unique_ptr<int> upi2(new int(4));
unique_ptr<T,D> u(d); //创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 delete
//所有权的变化
int *upi=upi2.release();//释放所有权
unique_ptr<string> us(new string("abc"));
unique_ptr<string> us2=std::move(us);//所有权转移,us所有权转移后,变成空指针
us2.reset(us.release());//所有权转移
us2=nullptr;//显式销毁所指对象,智能指针变为空指针,=us2.reset()
具体使用
unique_ptr 是个临时右值,编译器允许拷贝语义
unique_ptr<string> demo(const char* s) {
unique_ptr<string> temp (new string(s));
return temp;
}
// 假设编写了如下代码:
unique_ptr<string> ps;
ps = demo('Uniquely special");
demo函数返回一个临时 unique_ptr,然后 ps 接管了临时对象 unique_ptr 所管理的资源,而返回时临时的 unique_ptr 被销毁,也就是说没有机会使用 unique_ptr 来访问无效的数据,换句话来说,这种赋值是不会出现任何问题的,即没有理由禁止这种赋值
可以放在容器中
// 方式一
vector<unique_ptr<string>> vs { new string{“Doug”}, new string{“Adams”} };
// 方式二
vector<unique_ptr<string>>v;
unique_ptr<string> p1(new string("abc"));
管理动态数组
unique_ptr<int[]> p (new int[3]{1,2,3});
p[0] = 0;// 重载了operator[]
weak_ptr
weak_ptr 允许你共享但不拥有某对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何 weak_ptr 都会自动成空(empty)。因此,在 默认和 拷贝 构造函数之外,weak_ptr** 只提供 “接受一个 shared_ptr” 的构造函数**。不具备普通指针的行为,没有重载*和→运算符。
作用:能够协助share_ptr工作,获得资源的观测权。
weak_ptr<T> w;
weak_ptr<T> w(sp);//与shared_ptr指向相同的对象,shared_ptr的引用计数不变,
//T必须能转换为 sp 指向的类型
w.reset();//将w置空
w.use_count();//返回与w共享对象的share_ptr的数量
w.expired();//若 w.use_count() 为 0,返回 true,否则返回 false
w.lock();//若expired()为true,返回一个空shared_ptr,否则非空
具体使用
shared_ptr<int> sp(new int(10));
assert(sp.use_count()==1);
weak_ptr<int> wp(sp);
assert(wp.use_count()==1);
if(!wp.expired())//判断所观测的shared_ptr创建的对象是否失效
{
shared_ptr<int> sp2=wp.lock();
*sp2=100;
assert(wp.use_count()==2);
cout<<wp.use_count()<<endl;
}
assert(wp.use_count()==1);
cout<<"sp="<<*sp<<endl;
weak_ptr 能不能知道对象计数为 0,为什么?
不能。
weak_ptr只可以从shared_ptr或者另一个weak_ptr对象构造,是配合shared_ptr而引入的一种智能指针,只是提供了一种访问对象的手段,而不会对对象的内存进行管理,因为对象计数是由share_ptr管理的,weak_ptr的构造和析构不会影响计数多少。
weak_ptr 如何解决 shared_ptr 的循环引用问题?
多个shared_ptr对象可以同时托管一个指针,系统会维护一个托管计数。当无shared_ptr托管该指针时,delete该指针。shared_ptr存在一个问题,当两个shared_ptr指针相互引用时,那么这两个指针的引用计数不会下降为0,资源得不到释放。
weak_ptr可以检测share_ptr所管理对象是否被释放,从而避免非法访问。
class Parent;//需要提前声明
class child{
shared_ptr<Parent> shParent;
// weak_ptr<Parent> shParent;
public:
void setParent(shared_ptr<Parent> parent)
{
this->shParent=parent;
}
void dos()
{
if(this->shParent.use_count())
{
cout<<"and"<<endl;
}
}
~child(){}
};
class Parent{
shared_ptr<child> shchild;
// weak_ptr<child> shchild;
public:
void setchild(shared_ptr<child> child)
{
this->shchild=child;
}
void dos()
{
if(this->shchild.use_count())
{
cout<<"and"<<endl;
}
}
~Parent(){}
};
void testWeak_ptr()
{
weak_ptr<Parent> wpp;
weak_ptr<child>wpc;
{
shared_ptr<Parent> p(new Parent);
shared_ptr<child> c(new child);
p->setchild(c);
c->setParent(p);//循环引用
wpp=p;
wpc=c;
cout<<"p count="<<p.use_count()<<endl;//如果不设置为弱引用指针,那么将会时2
cout<<"c count="<<c.use_count()<<endl;//设置了为1
}
cout<<wpp.use_count()<<endl<<wpc.use_count()<<endl;//返回与 wpp,wpc 共享对象的 shared_ptr 的数量
}
make_shared
make_shared是一个模板函数;是一個能夠在动态内存中分配一个对象并初始化它,然后返回指向此对象的的share_ptr的模板函数
Y: 通过share_ptr管理内存,较为安全,方便,但存在一定开销,不过相对new分配来说更为高效;
https://blog.csdn.net/fengbingchun/article/details/52202007
W:
如
std::shared_ptr<Widget> spw(new Widget); 可以替换为
auto spw = std::make_shared<Widget>();
https://www.jianshu.com/p/03eea8262c11
//p1指向一个值为"9999999999"的string
shared_ptr<string> p1 = make_shared<string>(10, '9');
shared_ptr<string> p2 = make_shared<string>("hello");
shared_ptr<string> p3 = make_shared<string>();
https://blog.csdn.net/CPriLuke/article/details/79462791
make_shared是组合使用可变参数模板与forward(转发)机制实现将实参保持不变地传递给其他函数
make_shared模板的使用需要以“显示模板实参”的方式使用,如上题所示make_shared(10, 9),如果不传递显示 模板实参string类型,make_shared无法从(10, ‘9’)两个模板参数中推断出其创建对象类型。
实现自己的make_shared
使用shared_ptr实现
#include<bits/stdc++.h>
using namespace std;
//return shared_ptr
template<typename T,typename... Args>//可变参数模板
shared_ptr<T> my_shared_make(Args&&... args){
//形参为右值引用,传给T的构造函数形参使用 std::forward可保留形参属性
// new处新的内存空间,将控制权给shared_ptr构造函数
shared_ptr<T> res(new T(forward<Args>(args)...));
return res;
}
int main(int args,char** argc){
auto ip=my_shared_make<int>(42);
cout<<*(ip.get())<<endl;
cout<<*ip<<endl;
shared_ptr<string> st=my_shared_make<string>(10,'c');
cout<<*st<<endl;
return 0;
}
string s6=string(10,'c');//拷贝初始化,生成一个初始化好的对象,拷贝给s6
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
string s;//默认初始化,一个空字符串
string s1("ssss");//s1是字面值“ssss”的副本
string s2(s1);//s2是s1的副本
string s3=s2;//s3是s2的副本
string s4(10,'c');//把s4初始化
string s5="hiya";//拷贝初始化
string s6=string(10,'c');//拷贝初始化,生成一个初始化好的对象,拷贝给s6
//string s(cp,n)
char cs[]="12345";
string s7(cs,3);//复制字符串cs的前3个字符到s当中
//string s(s2,pos2)
string s8="asac";
string s9(s8,2);//从s2的第二个字符开始拷贝,不能超过s2的size
//string s(s2,pos2,len2)
string s10="qweqweqweq";
string s11(s10,3,4);//s4是s3从下标3开始4个字符的拷贝,超过s3.size出现未定义
return 0;
}
make_shared和shared_ptr的区别
struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);
std::shared_ptr在实现的时候使用的refcount技术,因此内部会有一个计数器(控制块,用来管理数据)和一个指针,指向数据。因此在执行std::shared_ptr<A> p2(new A)的时候,首先会申请数据的内存,然后申请内控制块,因此是两次内存申请,而std::make_shared<A>()则是只执行一次内存申请,将数据和控制块的申请放到一起。那这一次和两次的区别会带来什么不同的效果呢?
这使得make_shared效率更高,因为内存分配是代价很高的操作,参数求值的顺序不会异常。
但:
- 构造函数是保护或私有时,无法使用 make_shared
- 使用weak_ptr对象的内存无法及时回收
- 无法像shared_ptr 的构造那样添加一个delete
参考
https://blog.csdn.net/TuxedoLinux/article/details/85042775
http://www.manongjc.com/article/109595.html
c++11 shared_ptr 与 make_shared源码剖析
什么是万能引用?用途是什么?
auto&& ,类型推导,可以根据引用类型判断是否是左引用还是右引用
简化模板函数设计,使模板函数可以同时处理左值和右值。
3万+

被折叠的 条评论
为什么被折叠?



