天天看点

【知识补充】C++理论:类的继承,多态,模板,虚函数,智能指针

1. 三种类的继承方式

        面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。

        当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。 继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。

// 基类
class Animal {
    // eat() 函数
    // sleep() 函数
};


//派生类
class Dog : public Animal {
    // bark() 函数
};
           

        当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

1.1 公有继承public

        基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见;基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员可见,基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态;基类的私有成员不可见,基类的私有成员仍然是私有的,派生类不可访问基类中的私有成员。

        所以,在公有继承时,派生类的对象可以访问基类中的公有成员,派生类的成员函数可以访问基类中的公有成员和保护成员。简单来说,派生类能访问基类的public, protected成员,继承过来权限不变,派生类对象只能访问基类public成员。

测试代码如下:

class A
{
private:
    int m_data1;
    void print1() { cout << "private print1" << endl; }
protected:
    int m_data2;
    void print2() { cout << "protected print2" << endl; }
public:
    A(int x = 1, int y = 2, int z = 3) : m_data1(x), m_data2(y), m_data3(z) {}
    int m_data3;
    void print3() { cout << "protected print3" << endl; }
};

class B : public A
{
public:
    void test_public() {
        cout << m_data3 << endl;
        print3();
    }
    void test_protected() {
        cout << m_data2 << endl;
        print2();
    }
    void test_private() {
        // 下面两行编译不过,B类内无法访问父类的私有成员
        // cout << m_data1 << endl;  
        // print1();
    }
};


int main(int argc, char const* argv[])
{
    B b;
    b.test_public();
    b.test_protected();
    b.test_private();
    cout << b.m_data3 << endl;
    // cout << b.m_data2 << endl;  // 编译不过,子类对象无法访问父类protected的成员
    // cout << b.m_data1 << endl;  // 编译不过,子类对象无法访问父类private的成员
    return 0;
}
           

1.2 私有继承private 

        基类成员对其对象的可见性与一般类及其对象的可见性相同,公有成员可见,其他成员不可见;基类成员对派生类的可见性对派生类来说,基类的公有成员和保护成员是可见的,基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问;基类的私有成员是不可见的,派生类不可访问基类中的私有成员。

        基类成员对派生类对象的可见性对派生类对象来说,基类的所有成员都是不可见的。所以,在私有继承时,基类的成员只能由直接派生类访问,而无法再往下继承。简单来说派生类可以访问基类的public, protected成员,继承过来之后变成自己私有的。派生类的对象啥都不能访问。

class A
{
private:
    int m_data1;
    void print1() { cout << "private print1" << endl; }
protected:
    int m_data2;
    void print2() { cout << "protected print2" << endl; }
public:
    A(int x = 1, int y = 2, int z = 3) : m_data1(x), m_data2(y), m_data3(z) {}
    int m_data3;
    void print3() { cout << "protected print3" << endl; }
};

class B : private A
{
public:
    void test_public() {
        cout << m_data3 << endl;
        print3();
    }
    void test_protected() {
        cout << m_data2 << endl;
        print2();
    }
    void test_private() {
        // 下面两行编译不过,B类内无法访问父类的私有成员
        // cout << m_data1 << endl;  
        // print1();
    }
};


int main(int argc, char const* argv[])
{
    B b;
    b.test_public();
    b.test_protected();
    b.test_private();
    // cout << b.m_data3 << endl;  // // 编译不过,子类对象无法访问父类public的成员
    // cout << b.m_data2 << endl;  // 编译不过,子类对象无法访问父类protected的成员
    // cout << b.m_data1 << endl;  // 编译不过,子类对象无法访问父类private的成员
    return 0;
}
           

1.3 保护继承protected

        保护继承与私有是一样的。

public 的方式继承到派生类,这些成员的权限和在基类里的权限保持一致;

protected方式继承到派生类,成员的权限都变为protected;

private 方式继承到派生类,成员的权限都变为private;

        对于三种方式派生类的对象来说: 只有public的方式继承后,派生来的对象只能访问基类的public成员,protected和private方式继承,派生类的对象都不可以访问父类的成员。

2. 多态

2.1  多态的概念与分类

        多态(Polymorphisn)是面向对象程序设计(OOP)的一个重要特征。多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。在面向对象语言中,一个接口,多种实现即为多态。C++ 中的多态性具体体现在编译和运行两个阶段。编译时多态是静态多态,在编译时就可以确定使用的接口。运行时多态是动态多态,具体引用的接口在运行时才能确定。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

【知识补充】C++理论:类的继承,多态,模板,虚函数,智能指针

        静态多态和动态多态的区别其实只是在什么时候将函数实现和函数调用关联起来,是在编译时期还是运行时期,即函数地址是早绑定还是晚绑定的。静态多态是指在编译期间就可以确定函数的调用地址,并生产代码,这就是静态的,也就是说地址是早绑定。静态多态往往也被叫做静态联编。 动态多态则是指函数调用的地址不能在编译器期间确定,需要在运行时确定,属于晚绑定,动态多态往往也被叫做动态联编。

2.2 多态的作用

        为何要使用多态呢?封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。静态多态,将同一个接口进行不同的实现,根据传入不同的参数(个数或类型不同)调用不同的实现。动态多态,则不论传递过来的哪个类的对象,函数都能够通过同一个接口调用到各自对象实现的方法。

2.3 静态多态

        静态多态往往通过函数重载和模版(泛型编程)来实现,具体可见下面代码:

#include <iostream>
using namespace std;

//两个函数构成重载
int add(int a, int b){
    cout<<"in add_int_int()"<<endl;
    return a + b;
}
double add(double a, double b){
    cout<<"in add_double_doube()"<<endl;
    return a + b;
}

//函数模板(泛型编程)
template <typename T> T add(T a, T b){
    cout << "in func tempalte" << endl;
    return a + b;
}

int main(){
    cout<<add(1,1)<<endl;					//调用int add(int a, int b)
    cout<<add(1.1,1.1)<<endl;		   	 	//调用double add(double a, double b)
    cout<<add<char>('A',' ')<<endl;		//调用模板函数,输出小写字母a
}
           

2.4 动态多态

        动态多态最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而调用不同的方法。如果没有使用虚函数,即没有利用 C++ 多态性,则利用基类指针调用相应函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用同一个函数,这就无法达到“一个接口,多种实现”的目的了。

#include <iostream> 
using namespace std;

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()//虚函数,可以通过子类重写
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()//重写父类的虚函数
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};

class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};

// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();
 
   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   
   return 0;
}
           

输出结果:

Rectangle class area
Triangle class area
           

        通过上面的例子可以看出,在使用基类指针或引用指向子类对象时,调用的函数是子类中重写的函数,这样就实现了运行时函数地址的动态绑定,即动态联编。动态多态是通过“继承+虚函数”来实现的,只有在程序运行期间(非编译期)才能判断所引用对象的实际类型,根据其实际类型调用相应的方法。具体格式就是使用 virtual 关键字修饰类的成员函数时,指明该函数为虚函数,并且派生类需要重新实现该成员函数,编译器将实现动态绑定。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

需要注意:

  1. 只有类的成员函数才能声明为虚函数,虚函数仅适用于有继承关系的类对象。普通函数不能声明为虚函数。
  2. 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
  3. 内联函数(inline)不能是虚函数,因为内联函数不能在运行中动态确定位置。
  4. 构造函数不能是虚函数。
  5. 析构函数可以是虚函数,而且建议声明为虚函数。

2.5 重写 vs 重载

        重写可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。

2.6 隐藏

        除了重载与覆盖(重写),C++还有隐藏。隐藏是指派生类的函数屏蔽了与其同名的基类函数。隐藏规则如下:

  1. 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual 关键字,基类的函数将被隐藏(注意别与重载混淆,重载是在同一个类中发生)。
  2. 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆,覆盖有virtual关键字)。

2.7 模板

         模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

//函数模板的格式:
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{
    函数体
}
           

其中template和class是关见字,class可以用typename 关见字代替,在这里typename 和class没区别,<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。比如swap的模板函数形式为:

template <class T> void swap(T& a, T& b){},
           

        当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如swap(a,b)其中a和b是int 型,这时模板函数swap中的形参T就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当swap(c,d)其中c和d是double类型时,模板函数会被替换为swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。

类模板的格式为:

template<class  形参名,class 形参名,…>   class 类名
{ ... };
           

        类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如

template<class T> class A{public: T a; T b; T hy(T c, T &d);};
           

        在类A中声明了两个类型为T的成员变量a和b,还声明了一个返回类型为T带两个参数类型为T的函数hy。

3. 虚函数与纯虚函数

        虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。

        您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0;
};
           

= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。

4. 智能指针

        由于 C++ 语言没有自动内存回收机制,程序员每次

new

出来的内存都要手动

delete

。程序员忘记

delete

,流程太复杂,最终导致没有

delete

,异常导致程序过早退出,没有执行

delete

的情况并不罕见。

        对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了

operator->

操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用

.

操作符。

        访问智能指针包含的裸指针则可以用 

get()

 函数。由于智能指针是一个对象,所以

if (my_smart_object)

永远为真,要判断智能指针的裸指针是否为空,需要这样判断:

if (my_smart_object.get())

        智能指针包含了 

reset()

 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。

c++主要的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用。

4.1 auto_ptr

std::auto_ptr

 属于 STL,当然在 

namespace std

 中,包含头文件

#include<memory>

便可以使用。 

std::auto_ptr

 能够方便的管理单个堆内存对象。

我们从代码开始分析:

{
  std::auto_ptr<A> a_ptr(new A());  // 创建对象

  if(a_ptr.get())                   // 判断智能指针是否为空
  {
      a_ptr->show_message();        // 使用 operator-> 调用智能指针对象中的函数
      a_ptr.get()->show_message();  // 使用 get() 返回裸指针
  }
}                                   // a_ptr 生命周期结束,析构堆对象 A
           

上述为正常使用 std::auto_ptr 的代码,一切似乎都良好,无论如何不用我们显式使用该死的delete 了。

问题 1

void TestAutoPtr2() {
  std::auto_ptr<A> my_memory(new A(1));

  if (my_memory.get()) {
    std::auto_ptr<A> my_memory2;      // 创建一个新的 my_memory2 对象
    my_memory2 = my_memory;           // 复制旧的 my_memory 给 my_memory2
    my_memory2->show_message();       // 输出信息,复制成功
    my_memory->show_message();        // 崩溃
  }
}
           

最终如上代码导致崩溃,如上代码时绝对符合 C++ 编程思想的,居然崩溃了,跟进

std::auto_ptr

的源码后,我们看到,罪魁祸首是

my_memory2 = my_memory

,这行代码,

my_memory2

 完全夺取了 

my_memory

 的内存管理所有权,导致 

my_memory

 悬空,最后使用时导致崩溃。

所以,使用 std::auto_ptr 时,绝对不能使用“operator=”操作符。作为一个库,不允许用户使用,确没有明确拒绝,多少会觉得有点出乎预料。

问题 2

void TestAutoPtr3() {
  std::auto_ptr<A> my_memory(new A(1));

  if (my_memory.get()) {
    my_memory.release();
  }
}                // 没有析构
           

看到什么异常了吗?我们创建出来的对象没有被析构,导致内存泄露。当我们不想让

my_memory

继续生存下去,我们调用 

release()

 函数释放内存,结果却导致内存泄露(在内存受限系统中,如果

my_memory

占用太多内存,我们会考虑在使用完成后,立刻归还,而不是等到

my_memory

结束生命期后才归还)。

void TestAutoPtr3() {
  std::auto_ptr<A> my_memory(new A(1));
  if (my_memory.get()) {
    A* temp_memory = my_memory.release();
    delete temp_memory;
  }
}
           

4.2 unique_ptr

unique_ptr

 由 

C++11

 引入,旨在替代不安全的 

auto_ptr

unique_ptr

 是一种定义在头文件

<memory>

中的智能指针。

它持有对对象的独有权——两个

unique_ptr

不能指向一个对象,即 

unique_ptr

 不共享它所管理的对象。

它无法复制到其他

unique_ptr

,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。只能移动 

unique_ptr

,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个

unique_ptr

,并且原始 

unique_ptr

 不再拥有此资源。

实际使用中,建议将对象限制为由一个所有者所有,因为多个所有权会使程序逻辑变得复杂。因此,当需要智能指针用于存 C++ 对象时,可使用 

unique_ptr

,构造 

unique_ptr

 时,可使用 

make_unique

 Helper 函数。

下图演示了两个 unique_ptr 实例之间的所有权转换。

【知识补充】C++理论:类的继承,多态,模板,虚函数,智能指针

unique_ptr

 与原始指针一样有效,并可用于 STL 容器。将 

unique_ptr

 实例添加到 STL 容器运行效率很高,因为通过 

unique_ptr

 的移动构造函数,不再需要进行复制操作。

unique_ptr

 指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过 

reset

 方法重新指定、通过 

release

 方法释放所有权、通过移动语义转移所有权,

unique_ptr

 还可能没有对象,这种情况被称为 

empty

//智能指针的创建  
unique_ptr<int> u_i; 	//创建空智能指针
u_i.reset(new int(3)); 	//绑定动态对象  
unique_ptr<int> u_i2(new int(4));//创建时指定动态对象
unique_ptr<T,D> u(d);	//创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 delete

//所有权的变化  
int *p_i = u_i2.release();	//释放所有权  
unique_ptr<string> u_s(new string("abc"));  
unique_ptr<string> u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针” 
u_s2.reset(u_s.release());	//所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价
           

4.3 shared_ptr

shared_ptr

是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在

memory

文件中,命名空间为

std

。 

shared_ptr

最初实现于Boost库中,后由 

C++11

 引入到 

C++ STL

shared_ptr

 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 

shared_ptr

 共同管理同一个对象。 像 

shared_ptr

 这种智能指针,《Effective C++》称之为“引用计数型智能指针”(reference-counting smart pointer,RCSP)。

shared_ptr

 是为了解决 

auto_ptr

 在对象所有权上的局限性(

auto_ptr

 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:

  1. shared_ptr

     对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
  2. 时间上的开销主要在初始化和拷贝操作上, 

    *

     和 

    ->

     操作符重载的开销跟 

    auto_ptr

     是一样;
  3. 开销并不是我们不使用 

    shared_ptr

     的理由,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() { cout << i << " " << "destructed" << endl; }
};
int main()
{
    shared_ptr<A> sp1(new A(2)); //A(2)由sp1托管,
    shared_ptr<A> sp2(sp1);       //A(2)同时交由sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;   //A(2)同时交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A * p = sp3.get();      // get返回托管的指针,p 指向 A(2)
    cout << p->i << endl;  //输出 2
    sp1.reset(new A(3));    // reset导致托管新的指针, 此时sp1托管A(3)
    sp2.reset(new A(4));    // sp2托管A(4)
    cout << sp1->i << endl; //输出 3
    sp3.reset(new A(5));    // sp3托管A(5),A(2)无人托管,被delete
    cout << "end" << endl;
    return 0;
}
           

4.4 weak_ptr

weak_ptr

 被设计为与 

shared_ptr

 共同工作,可以从一个 

shared_ptr

 或者另一个 

weak_ptr

 对象构造而来。

weak_ptr

 是为了配合 

shared_ptr

 而引入的一种智能指针,它更像是 

shared_ptr

 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 

operator*

 和 

operator->

 ,因此取名为 weak,表明其是功能较弱的智能指针。

它的最大作用在于协助 

shared_ptr

 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 

weak_ptr

 只对 

shared_ptr

 进行引用,而不改变其引用计数,当被观察的 

shared_ptr

 失效后,相应的 

weak_ptr

 也相应失效。

使用 

weak_ptr

 的成员函数 

use_count()

 可以观测资源的引用计数,另一个成员函数 

expired()

 的功能等价于 

use_count()==0

,但更快,表示被观测的资源(也就是

shared_ptr

管理的资源)已经不复存在。

weak_ptr

可以使用一个非常重要的成员函数

lock()

从被观测的 

shared_ptr

 获得一个可用的 

shared_ptr

 管理的对象, 从而操作资源。但当 

expired()==true

 的时候,

lock()

 函数将返回一个存储空指针的 

shared_ptr

weak_ptr的基本用法总结如下:

weak_ptr<T> w;	 	     // 创建空 weak_ptr,可以指向类型为 T 的对象。
weak_ptr<T> w(sp);	   // 与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型。
w=p;				           // p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象。
w.reset();			       // 将 w 置空。
w.use_count();		     // 返回与 w 共享对象的 shared_ptr 的数量。
w.expired();		       // 若 w.use_count() 为 0,返回 true,否则返回 false。
w.lock();			         // 如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr
           

下面是一个简单的使用示例:

#include < assert.h>

#include <iostream>
#include <memory>
#include <string>
using namespace std;

int main()
{
	shared_ptr<int> sp(new int(10));
	assert(sp.use_count() == 1);
	weak_ptr<int> wp(sp); 	//从shared_ptr创建weak_ptr
	assert(wp.use_count() == 1);
	if (!wp.expired())		//判断weak_ptr观察的对象是否失效
	{
		shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr
		*sp2 = 100;
		assert(wp.use_count() == 2);
	}
	assert(wp.use_count() == 1);
	cout << "int:" << *sp << endl;
    return 0;
}
           

4.5 如何选择智能指针

文简单地介绍了 C++ 标准模板库 STL 中四种智能指针,当然,除了STL 中的智能指针,C++ 准标准库 Boost 中的智能指针,比如boost::scoped_ptr、boost::shared_array、boost:: intrusive_ptr 也可以在实际编程实践中拿来使用.

  1. 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr;
  2. 两个对象都包含指向第三个对象的指针 STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr;
  3. 如果程序不需要多个指向同一个对象的指针,则可使用

    unique_ptr

    。如果函数使用

    new

    分配内存,并返还指向该内存的指针,将其返回类型声明为

    unique_ptr

    是不错的选择。这样,所有权转让给接受返回值的 

    unique_ptr

    ,而该智能指针将负责调用 delete。可将 

    unique_ptr

     存储到 STL 容器中,只要不调用将一个 

    unique_ptr

     复制或赋值给另一个的算法(如 sort())。例如,可在程序中使用类似于下面的代码段。
unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}

void show(unique_ptr<int>& p1)
{
    cout << *p1 << ' ';
}

int main()
{
    ...
    vector<unique_ptr<int> > vp(size);
    for(int i = 0; i < vp.size(); i++)
		vp[i] = make_int(rand() % 1000);           // copy temporary unique_ptr
    vp.push_back(make_int(rand() % 1000));     // ok because arg is temporary
    for_each(vp.begin(), vp.end(), show);      // use for_each()
    ...
}
           

其中 

push_back

 调用没有问题,因为它返回一个临时 

unique_ptr

,该 

unique_ptr

 被赋给 vp 中的一个 

unique_ptr

另外,如果按值而不是按引用给 

show()

 传递对象,

for_each()

 将非法,因为这将导致使用一个来自 vp 的非临时 

unique_ptr

 初始化 pi,而这是不允许的。前面说过,编译器将发现错误使用 

unique_ptr

 的企图。

在 

unique_ptr

 为右值时,可将其赋给 

shared_ptr

,这与将一个 

unique_ptr

 赋给另一个 

unique_ptr

 需要满足的条件相同,即 

unique_ptr

 必须是一个临时对象。与前面一样,在下面的代码中,

make_int()

 的返回类型为 

unique_ptr<int>

unique_ptr<int> pup(make_int(rand() % 1000));   	// ok
shared_ptr<int> spp(pup);                    	    // not allowed, pup as lvalue
shared_ptr<int> spr(make_int(rand() % 1000));    	// ok
           

模板 

shared_ptr

 包含一个显式构造函数,可用于将右值 

unique_ptr

 转换为 

shared_ptr

shared_ptr

 将接管原来归 

unique_ptr

 所有的对象。

在满足 

unique_ptr

 要求的条件时,也可使用 

auto_ptr

,但 

unique_ptr

 是更好的选择。如果你的编译器没有 

unique_ptr

,可考虑使用 Boost 库提供的 

scoped_ptr

,它与 

unique_ptr

 类似。

参考文章:

C++ STL 四种智能指针_Dablelv的博客专栏-CSDN博客

C++ 智能指针详解 - 大气象 - 博客园

C11 智能指针

C++ 多态的两种形式

菜鸟教程

5分钟掌握多态的几种方式

继续阅读