C++11新特性

来源:互联网 发布:晨曦软件win10 编辑:程序博客网 时间:2024/05/16 09:24

C++11新特性

(1)对象构造的改良

在标准C++中,构造函数不能调用其它的构造函数;每个构造函数必须自己初始化所有的成员或是调用一个共用.的成员函数。基类的构造函数不能够直接作 为派生类的构造函数;就算基类的构造函数已经足够,每个派生的类仍必须实现自己的构造函数。类中non-constant的数据成员不能够在声明的地方被 初始化,它们只能在构造函数中被初始化。 C++11将会提供这些问题的解决方案。

C++11允许构造函数调用其他构造函数,这种做法称作委托或转接(delegation)。仅仅只需要加入少量的代码,就能让数个构造函数之间达成功能复用(reuse)。Java以及C都有提供这种功能。C++11语法如下:

class SomeType {

protected:

    int number;

    string name;

    SomeType(int i,string&s):number(i),name(s){}

public:

    SomeType(string& s):SomeType(1,s){ }

   

   

};

C++03中,构造函数运行退出代表对象构造完成;而允许使用转接构造函数的C++11则是以"任何"一个构造函数退出代表构造完成。使用转接的构造函数,函数本体中的代码将于被转接的构造函数完成后继续运行(如上例的PostInit())。若基底类使用了转接构造函数,则派生类的构造函数会在"所有"基底类的构造函数都完成后,才会开始运行。

(2)构造函数继承

C++11允许派生类手动继承基底类的构造函数,编译器可以使用基底类的构造函数完成派生类的构造。而将基类的构造函数带入派生类的动作,无法选择性地部分带入,要不就是继承基类全部的构造函数,要不就是一个都不继承(不手动带入)。此外,若牵涉到多重继承,从多个基底类继承而来的构造函数不可以有 相同的函数签名(signature)。而派生类的新加入的构造函数也不可以和继承而来的基底构造函数有相同的函数签名,因为这相当于重复声明。

class BaseClass

{

public:

    BaseClass(int iValue){

        cout<<"Base Constructor"<<endl;

    };

};

 

class DerivedClass : public BaseClass

{

public:

    using BaseClass::BaseClass;

};

此语法等同于DerivedClass声明一个DerivedClass(int)的构造函数。同时也因为DerivedClass有了一个继承而来的构造函数,所以不会有默认构造函数。

另一方面,C++11可以使用以下的语法完成成员初始化:

class SomeClass

{

public:

    SomeClass() {}

    explicit SomeClass(int iNewValue) :iValue(iNewValue) {}

   

private:

    int iValue = 5;

};

若是构造函数中没有设置iValue的初始值,则会采用类定义中的成员初始化,令iValue初值为5。在上例中,无参数版本的构造函数,iValue便采用默认所定义的值;而带有一个整数参数的构造函数则会以指定的值完成初始化。

成员初始化除了上例中的赋值形式(使用"="(,也可以采用构造函数以及统一形的初始化(uniform initialization,使用"{}")。

 

(3)显式虚函数重载

在C++里,在子类中容易意外的重载虚函数。举例来说:

struct Base {    
 virtual void some_func(); 
};   
struct Derived : Base {     
void some_func();
 }; 

Derived::some_func的真实意图为何?程序员真的试图重载该虚函数,或这只是意外?这也可能是base的维护者在其中加入了一个与Derived::some_func同名且拥有相同签名的虚函数。

另一个可能的状况是,当基类中的虚函数的签名被改变,子类中拥有旧签名的函数就不再重载该虚函数。因此,如果程序员忘记修改所有子类,运行期将不会正确调用到该虚函数正确的实现。

C++11将加入支持用来防止上述情形产生,并在编译期而非运行期捕获此类错误。为保持向后兼容,此功能将是选择性的。其语法如下:

struct Base {

    virtual void some_func(float);

};

 

struct Derived : Base {

   // virtual void some_func(int) override;   //錯誤格式:Derive::some_func並沒有overrideBase::some_func

    virtual void some_func(float) override; // OK:顯式改寫

};

编译器会检查基底类是否存在一虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。

C++11也提供指示字final,用来避免类被继承,或是基底类的函数被改写:

struct Base1 final { };

 

//struct Derived1 : Base1 { }; // 錯誤格式:classBase1已標明為final

 

struct Base2 {

    virtual void f() final;

};

 

struct Derived2 : Base2 {

   // void f(); // 錯誤格式:Base2::f已標明為final

};

以上的示例中,virtual void f() final;声明一新的虚拟函数,同时也表明禁止派生函数改写原虚拟函数。

overridefinal都不是语言关键字(keyword),只有在特定的位置才有特别含意,其他地方仍旧可以作为一般指示字(identifier)使用。

(4)Lambada 表达式

C++11 新增了很多特性,lambda 表达式是其中之一,如果你想了解的 C++11 完整特性,建议买一本书看看。

 

很多语言都提供了 lambda 表达式,如 Python,Java 8。lambda 表达式可以方便地构造匿名函数,如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。

 

C++11 的 lambda 表达式规范如下:

[ capture ] ( params )mutable exception attribute -> ret { body }     (1)

[ capture ] ( params )-> ret { body }     (2)        

[ capture ] ( params ) {body } (3)        

[ capture ] { body }       (4)        

 

其中

   (1) 是完整的 lambda 表达式形式,

   (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。

   (3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来:

     如果 lambda 代码块中包含了return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。

    如果没有 return 语句,则类似 voidf(...) 函数。

  (4)  省略了参数列表,类似于无参函数 f()。

 

mutable 修饰符说明 lambda 表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获对象的 non-const 方法。

exception 说明 lambda 表达式是否抛出异常(noexcept),以及抛出何种异常,类似于voidf() throw(X, Y)。

attribute 用来声明属性。

另外,capture 指定了在可见域范围内 lambda 表达式的代码内可见得外部变量的列表,具体解释如下:

[a,&b] a变量以值的方式呗捕获,b以引用的方式被捕获。

[this] 以值的方式捕获 this 指针。

[&] 以引用的方式捕获所有的外部自动变量。

[=] 以值的方式捕获所有的外部自动变量。

[] 不捕获外部的任何变量。

 

    std::vector<int> c {1,2,3,4,5,6,7 };

    int x = 5;

    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; } ),c.end());

   

    std::cout <<"c: ";

    for (auto i: c) {

        std::cout << i<<' ';

    }

    std::cout <<'\n';

   

    // the type of a closure cannot be named, but can beinferred with auto

    auto func1 = [](int i) {return i+4; };

    std::cout <<"func1: " << func1(6) <<'\n';

   

    // like all callable objects, closures can be captured instd::function

    // (this may incur unnecessary overhead)

    std::function<int(int)> func2 = [](int i) {return i+4; };

    std::cout <<"func2: " << func2(6) <<'\n';

   

   

    auto f = [] (int x,int y) { return x + y; };

    cout << f(21,12) << endl;

    return 0;

(5)std::function

我们知道,在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了operator()的对象(即C++98中的functor)。C++0x中,新增加了一个std::function对象,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。我们来看几个关于function对象的例子:

#include <functional>

std::function< size_t (const char*) > print_func;

 

/// normal function -> std::function object

size_t CPrint(const char*) { ... }

print_func = CPrint;

print_func("hello world"):

 

/// functor -> std::function object

class CxxPrint

{

public:

    size_toperator()(const char*) { ... }

};

CxxPrint p;

print_func = p;

print_func("hello world");

在上面的例子中,我们把一个普通的函数和一个functor赋值给了一个std::function对象,然后我们通过该对象来调用。其它的C++中的可调用实体都可以像上面一样来使用。通过std::function的包裹,我们可以像传递普通的对象一样来传递可调用实体,这样就很好解决了类型安全的问题。了解了std::function的基本用法,下面我们来看一些使用过程中的注意事项:

(1)关于可调用实体转换为std::function对象需要遵守以下两条原则:

 a. 转换后的std::function对象的参数能转换为可调用实体的参数

 b. 可高用实体的返回值能转换为std::function对象的(这里注意,所有的可调用实体的返回值都与返回void的std::function对象的返回值兼容)。

(2)std::function对象可以refer to满足(1)中条件的任意可调用实体

(3)std::function object最大的用处就是在实现函数回调,使用者需要注意,它不能被用来检查相等或者不相等

 (6) std::bind

bind是这样一种机制,它可以预先把指定可调用实体的某些参数绑定到已有的变量,产生一个新的可调用实体,这种机制在回调函数的使用过程中也颇为有用。C++98中,有两个函数bind1st和bind2nd,它们分别可以用来绑定functor的第一个和第二个参数,它们都是只可以绑定一个参数。各种限制,使得bind1st和bind2nd的可用性大大降低。C++0x中,提供了std::bind,它绑定的参数的个数不受限制,绑定的具体哪些参数也不受限制,由用户指定,这个bind才是真正意义上的绑定,有了它,bind1st和bind2nd就没啥用武之地了,因此C++0x中不推荐使用bind1st和bind2nd了,都是deprecated了。下面我们通过例子,来看看bind的用法:

using namespace std;

using namespace std::placeholders;

void f5(int i,int j)

{

    printf("i=%d,j=%d",i,j);

    if (i<j) {

        cout<<i<<endl;

    }else

    {

        cout<<j<<endl;

 

    }

}

auto f_bind=bind(f5,20,_1);

int main(int argc,const char * argv[])

{

 

    f_bind(6,56,77);

    f_bind(11,86,99);

 

    /*

     auto g = bind(f, a, b, _2, c,_1);

     g(X, Y) ;相当于调用了下面的

    f(a, b, Y, c, X);

    */

    return 0;

}

上面的例子中,bf1是把一个两个参数普通函数的第一个参数绑定为10,生成了一个新的一个参数的可调用实体体; bf2是把一个类成员函数绑定了类对象,生成了一个像普通函数一样的新的可调用实体; bf3是把类成员函数绑定了类对象和第二个参数,生成了一个新的std::function对象。看懂了上面的例子,下面我们来说说使用bind需要注意的一些事项:

 

(1)bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数,是pass-by-value的

(2)对于不事先绑定的参数,需要传std::placeholders进去,从_1开始,依次递增。placeholder是pass-by-reference的

(3)bind的返回值是可调用实体,可以直接赋给std::function对象

(4)对于绑定的指针、引用类型的参数,使用者需要保证在可调用实体调用之前,这些参数是可用的

(5)类的this可以通过对象或者指针来绑定

通过上面的介绍,我们基本了解了function,bind和lambda的用法,把三者结合起来,C++将会变得非常强大,有点函数式编程的味道了。最后,这里再补充一点,对于用bind来生成function和用lambda表达式来生成function, 通常情况下两种都是ok的,但是在参数多的时候,bind要传入很多的std::placeholders,而且看着没有lambda表达式直观,所以通常建议优先考虑使用lambda表达式。

 (7)智能指针

一、简介

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。

用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法。包括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost::intrusive_ptr。你可能会想,如此多的智能指针就为了解决new、delete匹配问题,真的有必要吗?看完这篇文章后,我想你心里自然会有答案。

下面就按照顺序讲解如上 7 种智能指针(smart_ptr)。

二、具体使用

1、总括

对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。

访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if(my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if(my_smart_object.get())。

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

我们编写一个测试类来辅助分析:

class Simple

{

public:

   Simple(int param=0)

    {

       number=param;

       std::cout<<"Simple:"<<number<<std::endl;

    }

   ~Simple(){

     std::cout <<"~Simple: " << number <<std::endl;

    }

   

    void PrintSomething()

    {

       std::cout <<"PrintSomething: " << info_extend.c_str() << std::endl;

    }

    std::stringinfo_extend;

    int number;

};

 

2、std::auto_ptr

std::auto_ptr 属于 STL,当然在 namespacestd 中,包含头文件 #include<memory> 便可以使用。std::auto_ptr 能够方便的管理单个堆内存对象。

我们从代码开始分析:

void TestAutoPtr()

{

    std::auto_ptr<Simple> my_memory(newSimple(1));////创建对象,输出:Simple1

    if (my_memory.get()) {// 判断智能指针是否为空

        my_memory->PrintSomething();  // 使用 operator->调用智能指针对象中的函数

        my_memory.get()->info_extend="Addion"// 使用 get()返回裸指针,然后给内部对象赋值

        my_memory.get()->PrintSomething(); // 再次打印,表明上述赋值成功

        (*my_memory).info_extend+="other"// 使用 operator*返回智能指针内部对象,然后用“.”调用智能指针对象中的函数

        my_memory->PrintSomething();

       // my_memory.release();

       

    }

}

执行结果为:

Simple: 1

PrintSomething:

PrintSomething: Addition

PrintSomething: Addition other

~Simple: 1

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

其实好景不长,我们看看如下的另一个例子:

voidTestAutoPtr2() {

  std::auto_ptr<Simple>my_memory(new Simple(1));

  if(my_memory.get()) {

    std::auto_ptr<Simple>my_memory2;   // 创建一个新的 my_memory2 对象

    my_memory2 =my_memory;             // 复制旧的 my_memory  my_memory2

    my_memory2->PrintSomething();       // 输出信息,复制成功

    my_memory->PrintSomething();        // 崩溃

  }

}

最终如上代码导致崩溃,如上代码时绝对符合 C++ 编程思想的,居然崩溃了,跟进std::auto_ptr 的源码后,我们看到,罪魁祸首是“my_memory2= my_memory”,这行代码,my_memory2 完全夺取了 my_memory 的内存管理所有权,导致 my_memory 悬空,最后使用时导致崩溃。

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

看完 std::auto_ptr 好景不长的第一个例子后,让我们再来看一个:

voidTestAutoPtr3() {

  std::auto_ptr<Simple>my_memory(new Simple(1));

 

  if(my_memory.get()) {

    my_memory.release();

  }

}

执行结果为:

Simple: 1

看到什么异常了吗?我们创建出来的对象没有被析构,没有输出“~Simple: 1”,导致内存泄露。当我们不想让 my_memory 继续生存下去,我们调用 release() 函数释放内存,结果却导致内存泄露(在内存受限系统中,如果my_memory占用太多内存,我们会考虑在使用完成后,立刻归还,而不是等到 my_memory 结束生命期后才归还)。

正确的代码应该为:

voidTestAutoPtr3() {

  std::auto_ptr<Simple>my_memory(new Simple(1));

  if (my_memory.get()){

    Simple*temp_memory = my_memory.release();

    deletetemp_memory;

  }

}

voidTestAutoPtr3() {

  std::auto_ptr<Simple>my_memory(new Simple(1));

  if(my_memory.get()) {

    my_memory.reset();  // 释放 my_memory 内部管理的内存

  }

}

原来 std::auto_ptr  release() 函数只是让出内存所有权,这显然也不符合 C++ 编程思想。

总结:std::auto_ptr 可用来管理单个对象的对内存,但是,请注意如下几点:

(1    尽量不要使用“operator=”。如果使用了,请不要再使用先前对象。

(2    记住 release() 函数不会释放对象,仅仅归还所有权。

(3    std::auto_ptr 最好不要当成参数传递(读者可以自行写代码确定为什么不能)。

(4    由于 std::auto_ptr 的“operator=”问题,有其管理的对象不能放入 std::vector等容器中。

(5    ……

使用一个 std::auto_ptr 的限制还真多,还不能用来管理堆内存数组,这应该是你目前在想的事情吧,我也觉得限制挺多的,哪天一个不小心,就导致问题了。

由于 std::auto_ptr 引发了诸多问题,一些设计并不是非常符合 C++ 编程思想,所以引发了下面 boost 的智能指针,boost 智能指针可以解决如上问题。

让我们继续向下看。

3、boost::scoped_ptr

boost::scoped_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。boost::scoped_ptr  std::auto_ptr 一样,可以方便的管理单个堆内存对象,特别的是,boost::scoped_ptr 独享所有权,避免了std::auto_ptr 恼人的几个问题。

我们还是从代码开始分析:

void TestScopePtr()

{

    boost::scoped_ptr<Simple> my_memory(newSimple(1));

    if (my_memory.get()) {

        my_memory->PrintSomething();

        my_memory.get()->info_extend ="Addition";

       

        my_memory->PrintSomething();

       

        (*my_memory).info_extend +=" other";

       

        my_memory->PrintSomething();

         //  my_memory.release();           //编译 error: scoped_ptr没有 release 函数

       std::auto_ptr<Simple> my_memory2;

      // my_memory2 = my_memory;        //编译 error: scoped_ptr没有重载 operator=,不会导致所有权转移

    }

}

首先,我们可以看到,boost::scoped_ptr 也可以像 auto_ptr 一样正常使用。但其没有release() 函数,不会导致先前的内存泄露问题。其次,由于 boost::scoped_ptr 是独享所有权的,所以明确拒绝用户写“my_memory2= my_memory”之类的语句,可以缓解 std::auto_ptr 几个恼人的问题。

由于 boost::scoped_ptr 独享所有权,当我们真真需要复制智能指针时,需求便满足不了了,如此我们再引入一个智能指针,专门用于处理复制,参数传递的情况,这便是如下的boost::shared_ptr

4、boost::shared_ptr

boost::shared_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。在上面我们看到 boost::scoped_ptr 独享所有权,不允许赋值、拷贝,boost::shared_ptr 是专门用于共享所有权的,由于要共享所有权,其在内部使用了引用计数。boost::shared_ptr 也是用于管理单个堆内存对象的。

我们还是从代码开始分析:

voidTestSharedPtr(boost::shared_ptr<Simple> memory) {  // 注意:无需使用 reference ( const reference)

  memory->PrintSomething();

  std::cout<< "TestSharedPtr UseCount: " << memory.use_count()<< std::endl;

}

 

voidTestSharedPtr2() {

  boost::shared_ptr<Simple>my_memory(new Simple(1));

  if(my_memory.get()) {

    my_memory->PrintSomething();

    my_memory.get()->info_extend= "Addition";

    my_memory->PrintSomething();

    (*my_memory).info_extend+= " other";

    my_memory->PrintSomething();

  }

 

  std::cout<< "TestSharedPtr2 UseCount: " << my_memory.use_count()<< std::endl;

  TestSharedPtr(my_memory);

  std::cout<< "TestSharedPtr2 UseCount: " << my_memory.use_count()<< std::endl;

 

  //my_memory.release();// 编译 error: 同样,shared_ptr 也没有 release 函数

}

执行结果为:

Simple: 1

PrintSomething:

PrintSomething:Addition

PrintSomething:Addition other

TestSharedPtr2UseCount: 1

PrintSomething:Addition other

TestSharedPtrUseCount: 2

TestSharedPtr2UseCount: 1

~Simple: 1

boost::shared_ptr 也可以很方便的使用。并且没有 release() 函数。关键的一点,boost::shared_ptr 内部维护了一个引用计数,由此可以支持复制、参数传递等。boost::shared_ptr 提供了一个函数 use_count() ,此函数返回 boost::shared_ptr 内部的引用计数。查看执行结果,我们可以看到在 TestSharedPtr2 函数中,引用计数为 1,传递参数后(此处进行了一次复制),在函数TestSharedPtr 内部,引用计数为2,在 TestSharedPtr 返回后,引用计数又降低为 1。当我们需要使用一个共享对象的时候,boost::shared_ptr 是再好不过的了。

在此,我们已经看完单个对象的智能指针管理,关于智能指针管理数组,我们接下来讲到。

5、boost::scoped_array

boost::scoped_array 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。

    boost::scoped_array 便是用于管理动态数组的。跟 boost::scoped_ptr 一样,也是独享所有权的。

我们还是从代码开始分析:

voidTestScopedArray() {

      boost::scoped_array<Simple>my_memory(new Simple[2]); // 使用内存数组来初始化

      if(my_memory.get()) {

        my_memory[0].PrintSomething();

        my_memory.get()[0].info_extend= "Addition";

        my_memory[0].PrintSomething();

        (*my_memory)[0].info_extend+= " other";            // 编译 error,scoped_ptr 没有重载operator*

        my_memory[0].release();                             // 同上,没有 release 函数

        boost::scoped_array<Simple>my_memory2;

        my_memory2 =my_memory;                             // 编译 error,同上,没有重载 operator=

      }

    }

boost::scoped_array 的使用跟 boost::scoped_ptr 差不多,不支持复制,并且初始化的时候需要使用动态数组。另外,boost::scoped_array 没有重载“operator*”,其实这并无大碍,一般情况下,我们使用 get() 函数更明确些。

下面肯定应该讲 boost::shared_array 了,一个用引用计数解决复制、参数传递的智能指针类。

6、boost::shared_array

boost::shared_array 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。

    由于 boost::scoped_array 独享所有权,显然在很多情况下(参数传递、对象赋值等)不满足需求,由此我们引入 boost::shared_array。跟 boost::shared_ptr 一样,内部使用了引用计数。

我们还是从代码开始分析:

voidTestSharedArray(boost::shared_array<Simple> memory) {  // 注意:无需使用 reference (或const reference)

  std::cout<< "TestSharedArray UseCount: " << memory.use_count()<< std::endl;

}

 

voidTestSharedArray2() {

  boost::shared_array<Simple>my_memory(new Simple[2]);

  if(my_memory.get()) {

    my_memory[0].PrintSomething();

    my_memory.get()[0].info_extend= "Addition 00";

    my_memory[0].PrintSomething();

    my_memory[1].PrintSomething();

    my_memory.get()[1].info_extend= "Addition 11";

    my_memory[1].PrintSomething();

    //(*my_memory)[0].info_extend+= " other";  // 编译 error,scoped_ptr 没有重载 operator*

  }

  std::cout<< "TestSharedArray2 UseCount: " << my_memory.use_count()<< std::endl;

  TestSharedArray(my_memory);

  std::cout<< "TestSharedArray2 UseCount: " << my_memory.use_count()<< std::endl;

}

执行结果为:

Simple: 0

Simple: 0

PrintSomething:

PrintSomething:Addition 00

PrintSomething:

PrintSomething:Addition 11

TestSharedArray2UseCount: 1

TestSharedArrayUseCount: 2

TestSharedArray2UseCount: 1

~Simple: 0

~Simple: 0

 boost::shared_ptr 一样,使用了引用计数,可以复制,通过参数来传递。

至此,我们讲过的智能指针有std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。这几个智能指针已经基本够我们使用了,90% 的使用过标准智能指针的代码就这 5 种。可如下还有两种智能指针,它们肯定有用,但有什么用处呢,一起看看吧。

7、boost::weak_ptr

boost::weak_ptr 属于 boost 库,定义在 namespace boost 中,包含头文件#include<boost/smart_ptr.hpp> 便可以使用。

在讲 boost::weak_ptr 之前,让我们先回顾一下前面讲解的内容。似乎boost::scoped_ptr、boost::shared_ptr 这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个 boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?

回答:有。首先 boost::weak_ptr 是专门为 boost::shared_ptr 而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr  boost::shared_ptr 的观察者(Observer)对象,观察者意味着 boost::weak_ptr 只对 boost::shared_ptr 进行引用,而不改变其引用计数,当被观察的 boost::shared_ptr 失效后,相应的 boost::weak_ptr 也相应失效。

我们还是从代码开始分析:

    void TestWeakPtr() {

      boost::weak_ptr<Simple>my_memory_weak;

      boost::shared_ptr<Simple>my_memory(new Simple(1));

 

      std::cout << "TestWeakPtrboost::shared_ptr UseCount: " << my_memory.use_count() <<std::endl;

      my_memory_weak = my_memory;

      std::cout << "TestWeakPtrboost::shared_ptr UseCount: " << my_memory.use_count() <<std::endl;

}

    执行结果为:

Simple: 1

TestWeakPtr boost::shared_ptrUseCount: 1

TestWeakPtr boost::shared_ptrUseCount: 1

~Simple: 1

    我们看到,尽管被赋值了,内部的引用计数并没有什么变化,当然,读者也可以试试传递参数等其他情况。

    现在要说的问题是,boost::weak_ptr 到底有什么作用呢?从上面那个例子看来,似乎没有任何作用,其实 boost::weak_ptr 主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr,用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的 boost::weak_ptr 是否为空就知道子类有没对自己赋值了,而不用影响子类 boost::shared_ptr 的引用计数,用以降低复杂度,更好的管理对象。

 

讲完如上 6 种智能指针后,对于一般程序来说 C++ 堆内存管理就够用了,现在有多了一种boost::intrusive_ptr,这是一种插入式的智能指针,内部不含有引用计数,需要程序员自己加入引用计数,不然编译不过(⊙﹏⊙b汗)。个人感觉这个智能指针没太大用处,至少我没用过。有兴趣的朋友自己研究一下源代码哦J

三、总结

如上讲了这么多智能指针,有必要对这些智能指针做个总结:

1、在可以使用 boost 库的场合下,拒绝使用 std::auto_ptr,因为其不仅不符合 C++ 编程思想,而且极容易出错。

2、在确定对象无需共享的情况下,使用 boost::scoped_ptr(当然动态数组使用boost::scoped_array)。

3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用boost::shared_array)。

4、在需要访问 boost::shared_ptr 对象,而又不想改变其引用计数的情况下,使用boost::weak_ptr,一般常用于软件框架设计中。

5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现 delete 关键字(或 C 语言的free 函数),因为可以用智能指针去管理。

 

0 0
原创粉丝点击