如何正确地使用C++指针

智能指针

当使用堆内存的时候,当不再需要某块堆内存时,必须释放这块内存,以便重新分配这块内存。
如果程序员在编写用于对象的重新分配的代码时,一不小心出现错误,会导致严重的问题,比如内存泄漏,那将导致程序崩溃。
在C ++ 11中,引入了自动管理内存的智能指针。当指针不在作用域内时,它们将不再使用的对象,自动重新分配。

unique_ptr

如果使用唯一的指针,则创建了一个对象;并且指针P1指向该对象,则只有一个指针可以一次指向该对象。因此,我们无法与其他指针共享,而是可以通过删除P1将控件转移到P2。
unique_ptr不共享其指针。不能将其复制到另一个unique_ptr;也不能按值传递给函数;或在任何需要复制的C ++标准库算法中使用。 unique_ptr只能移动。这意味着内存资源的所有权已转移到另一个unique_ptr,原始的unique_ptr不再拥有它。我们建议您将一个对象限制为一个所有者,因为多重所有权会增加程序逻辑的复杂性。因此,当您需要一个普通C ++对象的智能指针时,请使用unique_ptr,而当您构造一个unique_ptr时,请使用make_unique helper函数。
C ++标准库中;在头文件中定义unique_ptr。它与原始指针一样高效,并且可以在C ++标准库的容器中使用。将unique_ptr实例添加到C ++标准库容器中是有效的,因为unique_ptr的move构造函数消除了复制操作。

例1 unique_ptr的产生与移动

class Person {
public:
	string mFirstName;
	string mLastName;
	
	Person(string first_name, string last_name) : mFirstName(first_name), mLastName(last_name) {};
};

unique_ptr<Person> PersonFactory(const string& firstname, const string& lastname)
{
    // Implicit move operation into the variable that stores the result.
    return make_unique<Person>(firstname, lastname);
}

void check_quique_ptr()
{
    // 使用make_unique产生一个新unique_ptr指针.
    auto person = make_unique<Person>("john", "wang");

    // 引用unique_ptr指针.
    vector<string> lastnames = { person->mLastName };

    // 使用std::move移动一个unique_ptr到另一个unique_ptr.
    unique_ptr<Person> person_2 = move(person);

    // Obtain unique_ptr from function that returns by value.
    auto person_3 = PersonFactory("Michael", "Jackson");
    // 下列语句会产生编译错误
    //unique_ptr<Person> person_bad = person;
}

这个例子展示了unique_ptr的基本特征:可以移动;但不能复制。 “移动”是将对象的所有权转移到新的unique_ptr;并复位旧的unique_ptr。

下面展示一些 内联代码片

void check_unique_value_ref()
{
	// Create a unique_ptr to an array of 5 integers.
	auto p = make_unique<int[]>(5);
    vector<unique_ptr<Person>> persons;
    
    // 产生unique_ptr<Person> 实例,
    // 并加他们到一个vector中,使用隐含的move语义.
    persons.push_back(make_unique<Person>("jackson", "Liu")); 
    persons.push_back(make_unique<Person>("John", "Lee")); 

	// Initialize the array.
	for (int i = 0; i < 5; ++i)
	{
    	p[i] = i;
    	cout << p[i] << endl;
	}
	
    // 使用引用,避免使用复制语义.
    for (const auto& person : persons)
    {
        cout << person->mFirstName << endl;
        cout << person->mLastName << endl; 
    }    
}

使用unique_ptr引用传递的。不要使用值传递,否则发生编译错误。因为unique_ptr删除了复制构造函数。

class MyClass
{
private:
    // MyClass owns the unique_ptr.
    unique_ptr<ClassFactory> factory;
public:

    // Initialize by using make_unique with ClassFactory default constructor.
    MyClass() : factory (make_unique<ClassFactory>())
    {
    }

    void MakeClass()
    {
        factory->DoSomething();
    }
};

shared_ptr

如果使用shared_ptr,则一次可以有多个指针指向该对象,并且,它使用use_count()方法,维护引用计数器。
shared_ptr类型是C ++标准库中的智能指针,该指针专门用于可能需要多个所有者管理内存中对象生命周期的方案。初始化shared_ptr之后,可以复制它,在函数参数中按值传递它,并将其分配给其他shared_ptr实例。
所有实例都指向同一个对象,并且共享对一个“控制块”的访问权,每当添加新的shared_ptr,超出范围或对其进行重置时,该“控制块”都会递增和递减引用计数。当参考计数达到零时,控制块将删除内存资源及其本身。

// 尽可能使用make_shared产生shared_ptr.
auto sp1 = make_shared<Person>("John", "Liu");

// 使用new结果作为构造器变量
shared_ptr<Person> sp2(new Person("Jason", "Lee"));

shared_ptr<Person> sp5(nullptr);
sp5 = make_shared<Person>("John", "Liu");

shared_ptr使用

  1. 值传递
    调用复制构造函数,增加引用计数,并使被调用方成为所有者。此操作的开销很小,这可能很重要,具体取决于要传递的shared_ptr对象的数量。当调用方和被调用方之间的隐式或显式代码约定要求被调用方为所有者时,使用值传递。
    通过值传递shared_ptr意味着
  • 一个新的shared_ptr将被复制构造
  • 作为原子共享变量的引用计数增加
  • shared_ptr副本在函数末尾被销毁
  • 作为原子共享变量的引用计数减少
 void check_shared_ptr_pass_by_value(shared_ptr<Engineer> p)
 {
    cout << "value pass" << endl;
    cout << "   Engineer" << endl;
    cout << "      point = " << p.get() << endl;
    cout << "      count = " << p.use_count() << endl;
 
    shared_ptr<Person> lp = p;
    cout << "   Person" << endl;
    cout << "      point = " << lp.get() << endl;
    cout << "      count = " << lp.use_count() << endl;
 }

输出结果
value pass
   Engineer
      point = 0x7fffec9ade80
      count = 2
   Person
      point = 0x7fffec9ade80
      count = 3
  1. 引用或const引用
    在这种情况下,不会增加引用计数。只要调用者没有超出范围,被调用者就可以访问该指针。或者,被调用者可以根据引用决定创建一个shared_ptr,并成为共享所有者。当调用者不了解被调用者,或者,必须通过shared_ptr,并出于性能原因,而希望避免进行复制操作时,使用引用或const引用。

下面展示一些 内联代码片

void check_shared_ptr_pass(shared_ptr<Engineer> &p)
{
    cout << "reference pass" << endl;
    cout << "   Engineer" << endl;
    cout << "      point = " << p.get() << endl;
    cout << "      count = " << p.use_count() << endl;
 
    shared_ptr<Person> lp = p;
    cout << "   Person" << endl;
    cout << "      point = " << lp.get() << endl;
    cout << "      count = " << lp.use_count() << endl;
}

输出结果
reference pass
   Engineer
      point = 0x7fffec9ade80
      count = 1
   Person
      point = 0x7fffec9ade80
      count = 2
  1. 基类指针或引用传递给基类对象。
    被调用者可以使用该对象,但不能使它共享所有权,或延长生存期。如果被调用者从原始指针创建了shared_ptr,则新的shared_ptr与原始指针无关,并且不控制基础资源。当调用者和被调用者明确指定了调用者保留shared_ptr生命周期的所有权时,使用基类指针或引用传递给基类对象。
void check_shared_ptr_pass_base (const shared_ptr<Person> &p)
{
   cout << "base reference pass" << endl;
   cout << "   Person" << endl;
   cout << "      point = " << p.get() << endl;
   cout << "      count = " << p.use_count() << endl;
}

输出结果
base reference pass
   Person
      point = 0x7ffff21f4e80
      count = 2

在决定如何传递shared_ptr时,要确定被调用者是否必须共享基础资源的所有权。 “所有者”是一个对象或函数,可以根据需要保持其生存时间。

  • 如果调用者必须保证被调用者可以将指针的寿命延长到其(函数的)寿命之外,使用第一个选项。
  • 如果您不关心被调用者是否延长生命周期,通过引用传递,并让被调用者复制或不复制。
  • 如果必须授予辅助函数访问基础指针的权限,并且知道该辅助函数将仅使用该指针并在调用函数返回之前返回,则该函数不必共享基础指针的所有权。它只需要在调用者的shared_ptr的生存期内访问指针。在这种情况下,可以安全地传递引用shared_ptr,或者将原始指针或引用传递给基类对象。

有时,例如在std :: vector <shared_ptr >中,可能必须将每个shared_ptr传递给Lambda表达式主体,或命名函数对象。如果lambda或函数未存储指针,则通过引用传递shared_ptr,以避免为每个元素调用复制构造函数。

weak_ptr

除了不维护参考计数器外,它与shared_ptr非常相似。在这种情况下,指针不会对对象产生强烈的影响。原因是,如果假设指针持有该对象,并请求其他对象,则它们可能会形成死锁。

有时,对象必须存储一种访问shared_ptr的基类对象的方法,而不会导致引用计数增加。通常,当您在shared_ptr实例之间具有循环引用时,就会发生这种情况。

最好的设计是尽可能避免共享指针的所有权。但是,如果必须具有shared_ptr实例的共享所有权,请避免在它们之间使用循环引用。如果不可避免地要使用循环引用,或者出于某种原因,甚至更喜欢使用循环引用,请使用weak_ptr为一个或多个所有者提供对另一个shared_ptr的弱引用。通过使用weak_ptr,您可以创建一个shared_ptr,该shared_ptr连接到现有的一组相关实例,但前提是基类内存资源仍然有效。 soft_ptr本身不参与参考计数,因此,它不能防止参考计数变为零。但是,您可以使用weak_ptr来尝试获取使用其初始化的shared_ptr的新副本。如果内存已被删除,那么weak_ptr的bool运算符将返回false。如果内存仍然有效,则新的共享指针将增加引用计数,并保证只要shared_ptr变量保持作用域,内存就将有效。

创建weak_ptr作为shared_ptr的副本。它提供对一个或多个shared_ptr实例所拥有但不参与引用计数的对象的访问。 weak_ptr的存在或破坏对shared_ptr或其其他副本没有影响。在某些情况下,需要在shared_ptr实例之间中断循环引用。
循环依赖关系(shared_ptr的问题):让我们考虑一个场景,其中我们有两个类A和B,它们都具有指向其他类的指针。因此,总是像A指向B,B指向A。因此,use_count永远不会达到零,也永远不会被删除。
因此,在shared_ptr由于循环依赖而导致use_count永远不会为零的情况下,使用weak_ptr可以防止这种情况,这通过将A_ptr声明为weak_ptr来解决此问题,因此,类A不拥有它,只能访问它,并且我们还需要检查对象的有效性,因为它可能超出范围。通常,这是一个设计问题。

使用weak_ptr会引起错误

  1 #include <iostream>
  2 #include <memory>
  3
  4 using namespace std;
  5
  6 class Controller
  7 {
  8 public:
  9    int Num;
 10    string Status;
 11    weak_ptr<Controller> other;
 12
 13    explicit Controller(int i) : Num(i), Status("On")
 14    {
 15       cout << "Constructor" << Num << endl;
 16    }
 17
 18    ~Controller()
 19    {
 20       cout << "De-constructor" << Num << endl;
 21    }
 22
 23    void CheckStatuses() const
 24    {
 25        auto p = other.lock();
 26
 27        cout << "Status of " << p->Num << " = " << p->Status << endl;
 28    }
 29 };
 30
 31 void RunTest()
 32 {
 33
 34    shared_ptr<Controller> ctrl_1 = make_shared<Controller>(0);
 35    shared_ptr<Controller> ctrl_2 = make_shared<Controller>(1);
 36
 37    ctrl_1->other = weak_ptr<Controller>(ctrl_2);
 38    ctrl_2->other = weak_ptr<Controller>(ctrl_1);
 39
 40    ctrl_1->CheckStatuses();
 41    ctrl_2->CheckStatuses();
 42
 43    cout << "ctrl_1 use_count=" << ctrl_1.use_count() << endl;
 44    cout << "ctrl_2 use_count=" << ctrl_2.use_count() << endl;
 45 }
 46
 47 int main()
 48 {
 49    RunTest();
 50 }
 
输出结果
Constructor0
Constructor1
Status of 1 = On
Status of 0 = On
ctrl_1 use_count=1
ctrl_2 use_count=1
De-constructor1
De-constructor0

正确地使用weak_ptr

  1 #include <iostream>
  2 #include <memory>
  3
  4 using namespace std;
  5
  6 class Controller
  7 {
  8 public:
  9    int Num;
 10    string Status;
 11    shared_ptr<Controller> other;
 12
 13    explicit Controller(int i) : Num(i), Status("On")
 14    {
 15       cout << "Constructor" << Num << endl;
 16    }
 17
 18    ~Controller()
 19    {
 20        cout << "De-constructor" << Num << endl;
 21    }
 22
 23    void CheckStatuses() const
 24    {
 25        cout << "Status of " << other->Num << " = " << other->Status << endl;
 26    }
 27 };
 28
 29 void RunTest()
 30 {
 31
 32    shared_ptr<Controller> ctrl_1 = make_shared<Controller>(0);
 33    shared_ptr<Controller> ctrl_2 = make_shared<Controller>(1);
 34
 35    ctrl_1->other = shared_ptr<Controller>(ctrl_2);
 36    ctrl_2->other = shared_ptr<Controller>(ctrl_1);
 37
 38    ctrl_1->CheckStatuses();
 39    ctrl_2->CheckStatuses();
 40
 41    cout<<"ctrl_1 use_count=" << ctrl_1.use_count() << endl;
 42    cout<<"ctrl_2 use_count=" << ctrl_2.use_count() << endl;
 43 }
 44
 45 int main()
 46 {
 47    RunTest();
 48 }

输出结果
Constructor0
Constructor1
Status of 1 = On
Status of 0 = On
ctrl_1 use_count=2
ctrl_2 use_count=2

nullptr

nullptr是可在所有期望NULL的地方使用的关键字。像NULL一样,nullptr是隐含可转换的,并且可以与任何指针类型进行比较。
与NULL不同,它不能隐含转换或与整数类型媲美。

比较两个简单的指针时,有一些不确定,将两个类型为nullptr_t的值之间的比较定义为:

  • 通过<=和> =比较返回true
  • 通过<和>比较返回false
  • 将任何指针类型与nullptr, 比较==和!=
    如果分别为null或非null,则分别返回true或false。

下面展示一些 内联代码片

  1
  2 #include <iostream>
  3
  4 using namespace std;
  5
  6 void f(nullptr_t  p)
  7 {
  8     if (p)
  9        cout << "pointer is non-empty" << endl;
 10     else
 11        cout << "pointer is empty" << endl;
 12 }
 13
 14 void f(int val)
 15 {
 16     if (val == 0)
 17         cout << "val == 0" << endl;
 18     else
 19         cout << "val != 0" << endl;
 20 }
 21
 22 int main()
 23 {
 24     nullptr_t np1, np2;
 25
 26     if (np1 >= np2)
 27         cout << "can compare" << endl;
 28     else
 29         cout << "can not compare" << endl;
 30
 31     char *x = np1;
 32
 33     if (x == nullptr)
 34         cout << "x is null" << endl;
 35     else
 36         cout << "x is not null" << endl;
 37
 38     if (np1)
 39         cout << "bool is true" << endl;
 40     else
 41         cout << "bool is false" << endl;
 42
 43 //    f(NULL);     // overloaded ‘f(NULL)’ is ambiguous
 44
 45     f(nullptr);
 46     f(0);
 47
 48     return 0;
 49 }

输出结果
can compare
x is null
bool is false
pointer is empty
val == 0

C ++中的指针与引用

从表面上看,引用和指针非常相似,都用于使一个变量提供对另一变量的访问。两者都提供了许多相同的功能,因此通常不清楚这些不同机制之间的区别。

指针:指针是一个变量,用于保存另一个变量的内存地址。需要使用*运算符将指针取消引用,以访问其指向的内存位置。

引用:引用变量是别名,即已经存在的变量的另一个名称。引用(如指针)也通过存储对象的地址来实现。
可以认为引用是具有自动间接性的常量指针。

1.初始化:
指针可以通过以下方式初始化:

 int  val = 10;
 int *p =&val;

或者

 int val=10;
 int *p;
 p =&val;

就是说,我们可以在同一行;或多行中,说明和初始化指针。

引用可以通过下列的代码进行初始化

int a=10;
int &p=a;  //it is correct

但,下列的代码是错误的

int &p;
p=a;

指针可以直接分配为nullptr,而引用没有nullptr。引用无nullptr,不能重新赋值,确保基本操作不会遇到异常情况。

对指针可以执行各种算术运算,而对引用,没有所谓的算术运算。

指针与引用性能完全相同,因为引用在内部是作为指针实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值