c++问答 modernC++

C11 - cppreference.com

完美转发

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的区别和联系

发生在运行阶段

发生在编译阶段,提升运行效率

constconstexpr
修饰变量表示一个变量无法改变,但初值不确定,不能在编译阶段确定变量初值在编译阶段就确定,所以变量需要是一个常量表达式
修饰指针分为两种情况
顶层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

参考

string用法

可变参数包

https://blog.csdn.net/TuxedoLinux/article/details/85042775

make_shared实现

http://www.manongjc.com/article/109595.html

c++11 shared_ptr 与 make_shared源码剖析

什么是万能引用?用途是什么

auto&& ,类型推导,可以根据引用类型判断是否是左引用还是右引用
简化模板函数设计,使模板函数可以同时处理左值和右值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值