string类、智能指针

1. string 类 p531

1)构造函数

一个比较特殊的构造函数

template <class Iter>
string (Iter begin, Iter end); // 范围包括 begin 在内,但不包括 end

例如:

char c[] = "All's well that ends well";
...
string s(c+6, c+10);

注意,假设有另外一个 string 对象 s1:

string s1;
...
string s(s1+6, s1+10); // not allowed
string s(&s1[6], &s1[10]; // allowed

因为 s1 (对象名,不同于数组名)不会被看作是对象的地址,因此 s1 不是指针,所以 s1 + 6 是没有意义的;

而 s1[6] 是一个 char 字符,因此 &s1[6] 是一个地址,可以用作该构造函数的一个参数。

2)string 类的输入 p533

对于 C 风格字符串有 3 种输入方式

char info[100];
cin >> info;
cin.getline(info, 100);// read a line, discard \n
cin.get(info, 100);// read a line, leave \n in queue

对于 string 对象,有两种输入方式

string stuff;
cin >> stuff;
getline(cin, stuff);// read a line, discard queue

 string 版本的 getline() 函数从输入中读取字符,并将其存储到目标 string 中,直到发生下列三种情况之一:p534

  • 到达文件尾;这种情况下,输入流的 eofbit 将被设置,方法 fail() 和方法 eof() 都返回 true;
  • 遇到分界字符(默认为 \n);这种情况下,把分界字符从输入流中删除,但不存储它;
  • 读取字符的数量达到最大允许值(string::npos 和可供给分配的内存字节数中较小的一个),这种情况下,将设置输入流的 failbit 为1,fail() 返回 true;

string 版本的 operator>>() 函数的行为与此类似;只是它不断读取,直到遇到空白字符并将其留在输入队列中。(cin 不丢弃空白符,p68)

3)string 指定分界符

两个版本的 getline() 函数都有一个可选参数,用于指定使用哪个字符来确定输入的边界:

cin.getline(info, 100, ':'); // 读取直到遇到 :,并丢弃:
getline(stuff, ':'); // 读取直到遇到:,并丢弃:

注意,指定分界字符后,换行符 \n 将被视为常规字符。

例子:见 p534

3)使用字符串 p535

size(), length(), find()

capacity() // 返回当前分配给字符串的内存块的大小

reserve() // 请求内存块长度

 

2. 智能指针 p539

智能指针是行为类似于指针的类对象。

auto_ptr, unique_ptr, shard_ptr 都定义了类似指针的对象,可以将 new 获得(直接或间接)的地址赋给这种对象。

当智能指针过期时,其析构函数将使用 delete 来释放内存;在智能指针过期时,这些内存将自动被释放。

void remodel (std::string & str)
{
    std::string * ps = new std::string(str);
    ...
    if (weird_thing())
        throw exception();
    str = *ps;
    delete ps;
    return;
}

在上述代码中,当出现异常时,delete 将不被执行,将导致内存泄漏。

1)使用智能指针

要创建智能指针对象,必须包含头文件 memory;该文件包含模板定义,然后使用通常的模板语法来实例化所需类型的指针;

例如,模板 auto_ptr 包含如下构造函数

template<class X>
class auto_ptr {
public:
    explicit auto_ptr(X* p = 0) throw(); //throw() 表示构造函数不会引发异常

请求 X 类型的 auto_ptr 将获得一个指向 X 类型的 auto_ptr:

auto_ptr<double> pd(new double); // pd an auto_ptr to double(use in place of double * pd)
auto_ptr<string> ps(new string); // ps an auto_ptr to string(use in place of string * ps)

new double 是 new 返回的指针,指向新分配的内存块。它是构造函数 auto_ptr<double> 的参数,即对应于原型中 p 的实参。

其他两种智能指针使用同样的语法:

unique_ptr<double> pdu(new double); // pdu and unique_ptr to double
shared_ptr<string> pss(new string); //pss a shared_ptr to string

因此,可以使用智能指针来修改 remodel 函数:

#include <memory>
...
void  remodel (std::string & str)
{
    auto_ptr<string> ps (new string(str)); // new string 返回一个 new 申请的空string的地址;new string(str) 返回一个 new 申请的内容为 str 的 string 的地址
    ...
    if (weird_thing())
        throw exception();
    str = *ps;
    //delete ps; NO LONGER NEED
    return;
}

所有智能指针类都有一个 explicit 构造函数,该构造函数将指针作为参数;因此,不能自动地将指针转换为智能指针对象

shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // now allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)

shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); //allowed (explicit conversion)

注意,不能将非堆内存中地地址作为智能指针构造函数地参数

string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // NO!

pvac 过期时,程序将把 delete 运算符用作非堆内存,这是错误的。

2)智能指针的区分 p542

auto_ptr 智能指针在 c++11 中已被摒弃。

auto_ptr<string> ps (new string("I reigned lonely as a cloud."));
auto_ptr<string> vocation;
vocation = ps;

上述代码中,如果 ps 和 vocation 是常规指针,则两个指针指向同一个 string 对象,这样程序将试图删除同一个 string 对象两次。解决方法如下:

  • 定义赋值运算符,执行深复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本;
  • 建立所有权(ownership)概念;对于特定的对象,只有一个智能指针可以拥有它,只有拥有该对象的智能指针会删除该对象;让赋值操作转让所有权;这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
  • 跟踪引用特定对象的智能指针数,这称为引用计数(reference counting);仅当最后一个指针过期时,才调用 delete;这是 shared_ptr 采用的策略。

auto_ptr, shared_ptr, unique_ptr 的区分

 在 auto_ptr 放弃对象的所有权后,便不能再使用它来访问对象:

auto_ptr<string> s(new string("Fowl Balls"));
auto_ptr<string> q;
q = s;
cout << *s << endl;// not allowed

上面的程序中 auto_ptr s 将所有权转让给 q,因此 s 不再引用该字符串。

如果在上述程序中使用 shared_ptr 代替 auto_ptr,则程序将正常运行。在执行 q = s; 语句时,此时 shared_ptr s 和 shared_ptr q 指向同一个对象,引用计数从 1 增加到 2;在程序末尾,后声明的 q 首先调用其析构函数,该析构函数将引用计数降低到1;然后 s 调用析构函数时,将引用计数降低到 0,并释放该字符串对象所在的空间。

如果在上述程序中使用 unique_ptr,unique_ptr 与 auto_ptr 一样,也采用所有权模型;但使用 unique_ptr 时,程序不会等到运行阶段崩溃,而在编译阶段因执行下述代码而出现错误:

q = s;

探讨 auto_ptr 和 unique_ptr 之间的区别:

在下面的程序中

auto_ptr<string> p1 (new string("auto")); // #1
auto_ptr<string> p2;                     // #2
p2 = p1;                                 // #3

在语句 #3 中,p2 接管 string 对象的所有权后,p1 的所有权将被剥夺,这可以防止 p1 和 p2 的析构函数试图删除同一个对象;但如果程序随后试图使用 p1 将会出错,因为 p1 不再指向有效的数据;

而在

unique_ptr<string> p3 (new string("auto")); // #1
unique_ptr<string> p4;                     // #2
p4 = p3;                                   // #3

编译器认为 #3 非法,避免了 p3 不再指向有效数据的问题。因此 unique_ptr 比 auto_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,然后该临时 unique_ptr 将其指向的对象(即 "Uniquely special" 字符串)的所有权转让给 ps,紧接着该临时 unique_ptr 被销毁。

这没有问题,因为 ps 拥有了 string 对象的所有权;此外,demo() 返回的临时 unique_ptr 很快被销毁,没有机会使用它来访问无效的数据,没有理由禁止这种赋值。

编译器的确允许这种赋值!

总之,如果程序试图将一个 unique_ptr 赋给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这样做;如果源 unique_ptr 将存在一段时间,编译器将禁止这样做

using namespace std;
unique_ptr<string> pu1(new string("Hi ho!"));
unique_ptr<string> pu2;
pu2 = pu1;                                            // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string "Yo!"); // #2 allowed

语句 #1 将留下悬挂的 unique_ptr(pu1),这可能导致危害。语句 #2 不会留下悬挂的 unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权转让给 pu3 后就会被销毁。

unique_ptr 如何区分安全和不安全的用法?---> 它使用了 C++11 新增的移动构造函数和右值引用。

此外,unique_ptr 还有另一个优点:它有一个可用于数组的变体。

在之前提到,必须将 new 和 delete 配对使用,将 new [] 和 delete [] 配对使用;

模板 auto_ptr 使用 delete 而不是 lelete [],因此只能与 new 一起使用,而不能与 new [] 一起使用;但 unique_ptr 有使用 new [] 和 delete [] 的版本:

unique_ptr<double []> pda(new double(5)); // will use delete []

注意,使用 new 分配内存时,才能使用 auto_ptr 和 shared_ptr,使用 new [] 分配内存时,不能使用它们;不使用 new 分配内存时,不能使用 auto_ptr 或 shared_ptr;不使用 new 或 new [] 分配内存时,不能使用 unique_ptr。

 

posted @ 2022-05-27 19:55  SanFranciscoo  阅读(115)  评论(0编辑  收藏  举报