您的位置:首页 > 编程语言 > C语言/C++

左值和右值/auto ptr智能指针

2016-03-31 15:23 429 查看

左值和右值的区别

左值和右值的直观含义

左值必须应该是一个变量或者是表达式等,但是它的物理位置是可以确定的,而右值不一定,这也是它们两者之间的区别。

左值 (lvalue)和右值 (rvalue) 是 c/c++ 中一个比较晦涩基础的概念,有的人可能甚至没有听过,但这个概念到了 c++11 后却变得十分重要,它们是理解 move, forward 等新语义的基础。

在C++中,可以放到赋值操作符=左边的是左值,可以放到赋值操作符右边的是右值。有些变量既可以当左值又可以当右值。进一步来讲,左值为Lvalue,其实L代表Location,表示在内存中可以寻址,可以给它赋值(常量const类型也可以寻址,但是不能赋值),Rvalue中的R代表Read,就是可以知道它的值。

class cs
{
public:
cs(int i): i_(i)  { cout << "cs(" << i <<") constructor!" << endl; }
~cs() { cout << "cs destructor,i(" << i_ << ")" << endl; }

cs& operator=(const cs& other)
{
i_ = other.i_;
cout << "cs operator=()" << endl;
return *this;
}

int get_i() const { return i_; }
void change(int i)  { i_ = i; }

private:
int i_;
};

cs get_cs()
{
static int i = 0;
return cs(i++);
}

int main()
{
// 合法
(get_cs() = cs(2)).change(323);
get_cs() = cs(2);// operator=()
get_cs().change(32);

return 0;
}


右值能且只能被 const 类型的引用所指向

const cs& ref = get_cs();


另外值得注意的是,对于前面提到的右值的两个特性:

1) 允许调用成员函数。

2) 只能被 const reference 指向。

它们导致了一些比较有意思的结果,比如:

void func(cs& c)
{
cout << "c:" << c.get_i() << endl;
}

//error
func(get_cs());

//正确
func(get_cs() = get_cs());


其中: func(get_cs() = get_cs()); 能够被正常编译执行的原因就在于,cs 的成员函数 operator=() 返回的是 cs&!不允许非 const reference 引用 rvalue 并不是完美的,它事实上也引起了一些问题,比如说拷贝构造函数的接口不一致了,这是什么意思呢?

class cs
{
public:
cs& operator=(const cs& c);
};

// 另一种写法
class cs2
{
public:
cs2& operator=(cs2& c);
};


auto智能指针 auto ptr优点

#include <iostream>
#include <memory>
using namespace std;
class normal_pointer_example
{
public:
normal_pointer_example(){cout<<"构造函数执行!\n";}
~normal_pointer_example(){cout<<"析构函数执行!\n";}
};
class normal_pointer_wrong{};//normal_pointer_wrong异常
bool quit;
void quit_func()
{
if(quit==true)
cout<<"调用quit_func函数!\n";
throw normal_pointer_wrong();
}
int main()
{
try
{
auto_ptr<normal_pointer_example> Apointer (new normal_pointer_example);
//normal_pointer_example *Npointer=new normal_pointer_example();
//如果设置为auto_ptr,就会发现执行了析构函数.
quit=true;
quit_func();
//delete Npointer;
}
catch (normal_pointer_wrong)
{
/*  注意上面这个输出,我们看到当程序全部执行完了都没有能够调用析构函数将对象析构掉!这就说明并没有把对象delete掉!!这个就是问题,因为这样就有可能产生不易察觉的内存泄露。

针对上面这个情况,我们就要使用智能指针类auto_ptr来避免这种情况出现。*/
cout<<"输出normal_pointer_wrong异常!!\n";
}
return 0;
}


auto ptr的缺点

#include <iostream>
using namespace std;
template< class T>

class my_auto_ptr {

private:

T* m_ptr;

T* GetPtr(){ //供构造赋值时使用
T* tmp = m_ptr;
m_ptr = 0;
return tmp;
}

public:

explicit my_auto_ptr( T* p = 0) :m_ptr( p ) { }

~my_auto_ptr() { delete m_ptr; }

T& operator*() { return *m_ptr;}

T* operator->() { return m_ptr;}

my_auto_ptr(my_auto_ptr& mp){   //复制构造函数

m_ptr = mp.GetPtr(); //mp复制过来后它自己原来的指针相当于失效了.

}

my_auto_ptr& operator=(my_auto_ptr& ap){ //造型赋值操作符

if(ap != *this)
{
delete m_ptr;
m_ptr = ap.GetPtr();
}
return *this;
}

void reset(T* p){  //指针重置,相当于把指针指向另外一个地方去

if(p != m_ptr)

delete m_ptr;

m_ptr = p;

}

};
struct Arwen{

int age;

Arwen(int gg) :age(gg) { };

};

void main()

{

my_auto_ptr<Arwen> myPtr( new Arwen(24) );

int num =myPtr->age; //正确

my_auto_ptr<Arwen> ptrOne( myPtr);  //复制构造

//num =myPtr->age; 该处会出错.因为把myPtr复制给ptrOne后,它自己本身相当于失效了

num = ptrOne->age; //正确

my_auto_ptr<Arwen> ptrTwo = ptrOne;

//num = ptrOne->age;该处也会出错,此时ptrOne也失效了

num = ptrTwo->age; //正确

Arwen* pArwen = new Arwen( 88 );

ptrTwo.reset( pArwen);

num = pArwen->age; //此处的值是88了,而不是以前的24
return 0;
}

/*
上面我实现的my_auto_ptr基本上是实现了auto_ptr的所有核心功能.从里面我们可以明显的看到一个很大缺陷.我们看到当通过复构造函数,通过操作符=赋值后,原来的那个智能指针对象就失效了.只有新的智能指针对象可以有效使用了.用个专业点的说法叫所有权的转移.被包装的指针指向的内存块就像是一份独享占用的财产,当通过复制构造,通过=赋值传给别的智能指针对象时,原有的对象就失去了对那块内存区域的拥有权.也就是说任何时候只能有一个智能指针对象指向那块内存区域,不能有两个对象同时指向那块内存区域.

这样一来auto_ptr不能做为STL中容器的元素,为啥呢? 因为容器中经常存在值拷贝的情况嘛,把一个容器对象直接赋值给另一对象.完了之后两个容器对象可得都能用啊.而如果是auto_ptr的话显然赋值后只能一个有用,另一个完全报废了.另外比如你有个变量auto_ptr<int> ap( new int(44) );  然后ap被放进一个容器后,ap就报废不能用了.

*/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言 智能指针