《C++ Primer》第12章 12.1节习题答案2

本篇博客详细探讨了C++ Primer中关于动态内存和智能指针的使用,包括shared_ptr和unique_ptr的特性与陷阱。通过一系列练习,阐述了如何正确管理和避免内存泄漏,以及理解智能指针与普通指针的区别。
摘要由CSDN通过智能技术生成

《C++ Primer》第12章 动态内存

12.1节动态内存与智能指针习题答案

练习12.11:如果我们像下面这样调用process,会发生什么?

process(shared_ptr<int>(p.get()));

【出题思路】

理解智能指针和普通指针不能混用。

【解答】

此调用是错误的。p.get()获得一个普通指针,指向p所共享的int对象。利用此指针创建一个shared_ptr,而不是利用p创建一个shared_ptr,将不会形成正确的动态对象共享。编译器会认为p和ptr是使用两个地址(虽然它们相等)创建的两个不相干的shared_ptr,而非共享同一个动态对象。这样,两者的引用计数均为1。当process执行完毕后,ptr的引用计数减为0,所管理的内存地址被释放,而此内存就是p所管理的。p成为一个管理空悬指针的shared_ptr。

练习12.12:p和sp的定义如下,对于接下来的对process的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));

【解答】

(a)合法。sp是一个共享指针,指向一个int对象。对process的调用会拷贝sp,传递给process的参数ptr,两者都指向相同的int对象,引用计数变为2。当process执行完毕时,ptr被销毁,引用计数变回1。

(b)合法。new创建了一个int对象,指向它的指针被用来创建了一个shared_ptr,传递给process的参数ptr,引用计数为1。当process执行完毕,ptr被销毁,引用计数变为0,临时int对象因而被销毁。不存在内存泄漏和空悬指针的问题。

(c)不合法。不能将int*转换为shared_ptr<int>。

(d)合法,但是错误的程序。p是一个指向int对象的普通指针,被用来创建一个临时shared_ptr,传递给process的参数ptr,引用计数为1。当process执行完毕,ptr被销毁,引用计数变为0,int对象被销毁。p变为空悬指针。

练习12.13:如果执行下面的代码,会发生什么?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

【出题思路】

继续理解智能指针和普通指针使用上的问题。

【解答】

第二行用get获取了sp指向的int对象的地址,第三行用delete释放这个地址。这意味着sp的引用计数仍为1,但其指向的int对象已经被释放了。sp成为类似空悬指针的shared_ptr。

练习12.14:编写你自己版本的用shared_ptr管理connection的函数。

【出题思路】

本题练习利用智能指针管理使用资源的类,避免内存泄漏等问题。

【解答】

参照本节内容设计函数即可。main函数分别调用了未使用和使用了shared_ptr的版本,根据输出可以看出,前者未调用disconnect,而后者调用了。注意观察f1的输出中的换行,很明显,disconnect是在f1结束后(最后一条输出换行的语句已经执行完),在销毁p时被调用的。

#include <iostream>
#include <memory>

using namespace std;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
    cout << "打开连接" << endl;
    return connection();
}

void disconnect(connection c)
{
    cout << "关闭连接" << endl;
}

//未使用shared_ptr的版本
void f(destination &d)
{
    cout << "直接管理connect" << endl;
    connection c = connect(&d);
    //忘记调用disconnect关闭连接
    cout << endl;
}

void end_connection(connection *p)
{
    disconnect(*p);
}

//使用shared_ptr的版本
void f1(destination &d)
{
    cout << "用shared_ptr管理connect" << endl;
    connection c = connect(&d);

    shared_ptr<connection> p(&c, end_connection);
    //忘记调用disconnect关闭连接
    cout << endl;
}

int main(int argc, const char * argv[])
{
    destination d;
    f(d);
    f1(d);
    return 0;
}

运行结果:

练习12.15:重写上一题的程序,用lambda(参见10.3.2节,第346页)代替end_connection函数。

【出题思路】

复习lambda。

【解答】

根据end_connection的定义,lambda不捕获局部变量,参数为connection指针,用该指针指向的对象调用disconnect即可:

#include <iostream>
#include <memory>


using namespace std;

struct destination {};
struct connection {};

connection connect(destination *pd)
{
    cout << "打开连接" << endl;
    return connection();
}


void disconnect(connection c)
{
    cout << "关闭连接" << endl;
}

//未使用shared_ptr的版本
void f(destination &d)
{
    cout << "直接管理connect" << endl;
    connection c = connect(&d);
    //忘记调用disconnect关闭连接
    cout << endl;
}

void end_connection(connection *p)
{
    disconnect(*p);
}

//使用shared_ptr的版本
void f1(destination &d)
{
    cout << "用shared_ptr管理connect" << endl;
    connection c = connect(&d);
    shared_ptr<connection> p(&c, [](connection *p) {disconnect(*p); });
    //忘记调用disconnect关闭连接
    cout << endl;
}

int main(int argc, const char * argv[])
{
    destination d;
    f(d);
    f1(d);
    return 0;
}

运行结果:

 练习12.16:如果你试图拷贝或赋值unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。

【出题思路】

深入地理解unique_ptr不能拷贝或赋值的限制。

【解答】

#include <iostream>
#include <memory>
#include <string>

using namespace std;

int main()
{
    std::unique_ptr<string> str1(new string("23"));
    std::unique_ptr<string> str2;
    str2 = str1;

    return 0;
}

VS编译器出现这种错误提示

 即,程序调用了删除的函数。原因是,标准库为了禁止unique_ptr的拷贝和赋值,将其拷贝构造函数和赋值函数声明为了delete的:

unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

练习12.17:下面的unique_ptr声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);                                (b) IntP p1(pi);
(c) IntP p2(pi2);                               (d) IntP p3(&ix);
(e) IntP p4(new int(2048));                     (f) IntP p5(p2.get());

【出题思路】

继续熟悉unique_ptr使用上应注意的问题。

【解答】

(a)不合法。unique_ptr需要用一个指针初始化,无法将int转换为指针。

(b)合法。可以用一个int *来初始化IntP,但此程序逻辑上是错误的。它用一个普通int变量的地址初始化p1,p1销毁时会释放此内存,其行为是未定义的。

(c)合法。用一个指向动态分配的对象的指针来初始化IntP是正确的。(d)合法。但存在与(b)相同的问题。

(d)合法。但存在与(b)相同的问题。

(e)合法。与(c)类似。

(f)合法。但用p2管理的对象的地址来初始化p5,造成两个unique_ptr指向相同的内存地址。当其中一个unique_ptr被销毁(或调用reset释放对象)时,该内存被释放,另一个unique_ptr变为空悬指针。

练习12.18:shared_ptr为什么没有release成员?

【出题思路】

理解unique_ptr和shared_ptr的差别。

【解答】

unique_ptr“独占”对象的所有权,不能拷贝和赋值。release操作是用来将对象的所有权转移给另一个unique_ptr的。而多个shared_ptr可以“共享”对象的所有权。需要共享时,可以简单拷贝和赋值。因此,并不需要release这样的操作来转移所有权。

练习12.19:定义你自己版本的StrBlobPtr,更新StrBlob类,加入恰当的friend声明及begin和end成员。

【出题思路】

熟悉weak_ptr的使用。

【解答】

参考本节内容实现所需程序即可。为了实现对StrBlob的vector中元素的遍历,还定义了eq和neq两个函数来比较两个StrBlobPtr是否指向相同vector的相同位置。

#ifndef SYSTRBLOB_19_H
#define SYSTRBLOB_19_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

//提前声明,StrBlob中的友类声明所需
class StrBlobPtr;

class StrBlob
{
    friend class StrBlobPtr;

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> i1);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    //添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    //元素访问
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;

    //提供给StrBlobPtr的接口
    StrBlobPtr begin(); //定义StrBlobPtr后才能定义这两个函数
    StrBlobPtr end();

private:
    shared_ptr<std::vector<std::string>> data;
    //如果data[i]不合法,抛出一个异常
    void check(size_type i, const std::string &msg) const;
};

inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }

StrBlob::StrBlob(initializer_list<string> i1): data(make_shared<vector<string>>(i1)) { }

inline void StrBlob::check(size_type i, const string &msg) const
{
    if(i >= data->size())
        throw out_of_range(msg);
}

inline string& StrBlob::front()
{
    //如果vector为空,check会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}

//const版本front
inline const string& StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->back();
}

inline string& StrBlob::back()
{
    check(0, "back on emyty StrBlob");
    return data->back();
}

//const版本back
inline const string& StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

inline void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr
{
    friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
    StrBlobPtr(): curr(0) { }
    StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }

    string& deref() const;
    StrBlobPtr& incr(); //前缀递增
    StrBlobPtr& decr(); //前缀递减

private:
    //若检查成功,check返回一个指向vector的shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;

    //保存一个weak_ptr,意味着底层vector可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;//在数组中的当前位置
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();//vector还存在吗?
    if(!ret)
        throw runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw out_of_range(msg);
    return ret;//否则,返回指向vector的shared_ptr
}

inline string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];//(*p)是对象所指向的vector
}

//前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
    //如果curr已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr; //推进当前位置
    return *this;
}

//前缀递减:返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{
    //如果curr已经为0,递减它就会产生一个非法下标
    --curr;//递减当前位置
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

//StrBlob的begin和end成员的定义
inline StrBlobPtr StrBlob::begin()
{
    return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

//StrBlobPtr的比较操作
inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    //若底层的vector是同一个
    if(l == r)
        //则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;//若指向不同vector,则不可能相等
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    return !eq(lhs, rhs);
}

#endif // SYSTRBLOB_19_H
#include <iostream>
#include "SYStrBlob_19.h"

int main(int argc, const char * argv[])
{
    StrBlob b1;
    {
        StrBlob b2 = {"a", "an", "the"};
        b1 = b2;
        b2.push_back("about");
        cout << b2.size() << endl;
    }
    cout << b1.size() << endl;
    cout << b1.front() << " " << b1.back() << endl;

    const StrBlob b3 = b1;
    cout << b3.front() << " " << b3.back() << endl;

    for(auto it = b1.begin(); neq(it, b1.end()); it.incr())
        cout << it.deref() <<endl;

    return 0;
}

运行结果:

 练习12.20:编写程序,逐行读入一个输入文件,将内容存入一个StrBlob中,用一个StrBlobPtr打印出StrBlob中的每个元素。

【出题思路】

本题练习使用StrBlob和StrBlobPtr。

【解答】

用getline逐行读取输入文件,存入StrBlob后,用StrBlobPtr从StrBlob的begin遍历到end,逐个打印每个字符串即可。

#include <iostream>
#include <fstream>
#include "SYStrBlob_19.h"

using namespace std;


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

    ifstream in(argv[1]);
    cout << "argv[1]=============" << argv[1] << endl;
    if(!in)
    {
        cout << "无法打开输入文件" << endl;
        return -1;
    }

    StrBlob b;
    string s;
    while(getline(in, s))
    {
        b.push_back(s);
    }

    for(auto it = b.begin(); neq(it, b.end()); it.incr())
    {
        cout << it.deref() << endl;
    }

    return 0;
}

data12_20.txt文件内容如下:

Alice Emma has long flowing red hair.
Her Daddy says when the wind blows
through her hair, it looks almost alive,
like a fiery bird in flight.
A beautiful fiery bird, he tells her,
magical but untamed.
"Daddy, shush, there is no such thing,"
she tells him, at the same time wanting
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"

设置命令行参数:

 运行结果:

 练习12.21:也可以这样编写StrBlobPtr的deref成员:

std::string& deref() const
{ return (*check(curr, "dereference past end"))[curr]; }

你认为哪个版本更好?为什么?

【出题思路】

思考合法性检查不同方式的优缺点。

【解答】

书中的方式更好一些。将合法性检查与元素获取和返回语句分离开来,代码更清晰易读,当执行到第二条语句时,已确保p是存在的vector,curr是合法的位置,可安全地获取元素并返回。这种清晰的结构也更有利于修改不同的处理逻辑。而本题中的版本将合法性检查和元素获取及返回合在一条语句中,不易读,也不易修改。

练习12.22:为了能让StrBlobPtr使用const StrBlob,你觉得应该如何修改?定义一个名为ConstStrBlobPtr的类,使其能够指向const StrBlob。

【出题思路】

本题练习设计const版本。

【解答】

首先,为StrBlobPtr定义能接受const StrBlob &参数的构造函数:

[插图]

其次,为StrBlob定义能操作const对象的begin和end。声明:

StrBlobPtr(const StrBlob &a, size_t sz=0): wptr(a.data), curr(sz) {}

其次,为StrBlob定义能操作const对象的begin和end。

声明:

StrBlobPtr begin() const;
StrBlobPtr end() const;

定义:

inline StrBlobPtr StrBlob::begin() const
{
	return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const
{
	auto ret = StrBlobPtr(*this, data->size());
	return ret;
}

完整实现如下:

#ifndef SYSTRBLOB_22_H
#define SYSTRBLOB_22_H

#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>

using namespace std;

//提前声明,StrBlob中的友类声明所需
class StrBlobPtr;

class StrBlob
{
    friend class StrBlobPtr;

public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> i1);
    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    //添加和删除元素
    void push_back(const string &t) { data->push_back(t); }
    void pop_back();
    //元素访问
    string& front();
    const string& front() const;
    string& back();
    const string& back() const;

    //提供给StrBlobPtr的接口
    StrBlobPtr begin() const; //定义StrBlobPtr后才能定义这两个函数
    StrBlobPtr end() const;

private:
    shared_ptr<std::vector<std::string>> data;
    //如果data[i]不合法,抛出一个异常
    void check(size_type i, const std::string &msg) const;
};

inline StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }

StrBlob::StrBlob(initializer_list<string> i1): data(make_shared<vector<string>>(i1)) { }

inline void StrBlob::check(size_type i, const string &msg) const
{
    if(i >= data->size())
        throw out_of_range(msg);
}

inline string& StrBlob::front()
{
    //如果vector为空,check会抛出一个异常
    check(0, "front on empty StrBlob");
    return data->front();
}

//const版本front
inline const string& StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->back();
}

inline string& StrBlob::back()
{
    check(0, "back on emyty StrBlob");
    return data->back();
}

//const版本back
inline const string& StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

inline void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

//当试图访问一个不存在的元素时,StrBlobPtr抛出一个异常
class StrBlobPtr
{
    friend bool eq(const StrBlobPtr&, const StrBlobPtr&);
public:
    StrBlobPtr(): curr(0) { }
                //这里加上
    StrBlobPtr(const StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { }

    string& deref() const;
    StrBlobPtr& incr(); //前缀递增
    StrBlobPtr& decr(); //前缀递减

private:
    //若检查成功,check返回一个指向vector的shared_ptr
    shared_ptr<vector<string>> check(size_t, const string&) const;

    //保存一个weak_ptr,意味着底层vector可能会被销毁
    weak_ptr<vector<string>> wptr;
    size_t curr;//在数组中的当前位置
};

inline shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();//vector还存在吗?
    if(!ret)
        throw runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw out_of_range(msg);
    return ret;//否则,返回指向vector的shared_ptr
}

inline string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];//(*p)是对象所指向的vector
}

//前缀递增:返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
    //如果curr已经指向容器的尾后位置,就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr; //推进当前位置
    return *this;
}

//前缀递减:返回递减后的对象的引用
inline StrBlobPtr& StrBlobPtr::decr()
{
    //如果curr已经为0,递减它就会产生一个非法下标
    --curr;//递减当前位置
    check(-1, "decrement past begin of StrBlobPtr");
    return *this;
}

//StrBlob的begin和end成员的定义
inline StrBlobPtr StrBlob::begin() const//这里加上
{
    return StrBlobPtr(*this);
}

inline StrBlobPtr StrBlob::end() const//这里加上
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

//StrBlobPtr的比较操作
inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
    //若底层的vector是同一个
    if(l == r)
        //则两个指针都是空,或者指向相同元素时,它们相等
        return (!r || lhs.curr == rhs.curr);
    else
        return false;//若指向不同vector,则不可能相等
}

inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs)
{
    return !eq(lhs, rhs);
}


#endif // SYSTRBLOB_22_H
#include <iostream>
#include <fstream>
#include "SYStrBlob_22.h"

using namespace std;

int main(int argc, const char * argv[])
{
    const StrBlob b = {"Hello", "World", "!"};

    for(auto it = b.begin(); neq(it, b.end()); it.incr())
    {
        cout << it.deref() << endl;
    }

    return 0;
}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值