面试官的动机——实现智能指针1:unique_ptr

要求面试者实现一个简单的unique_ptr,能够考察其对许多知识点的掌握,尤其是《C++ Primer》第五版12~16章中介绍的内容。C++初学者学习完这些内容后,应该动手实现一遍unique_ptrshared_ptr。下面列出需要注意的知识点:

  1. 异常
  2. unique_ptr本身功能
  3. 三五法则和阻止拷贝
  4. 隐式的类类型转换
  5. 移动构造、移动赋值及自赋值问题
  6. 编写模板类。

除以上几点外,还有若干细节需要注意。接下来的代码将逐步呈现它们。


初始版本.

首先应该查看unique_ptr 的接口文档,然后,实现其一部分构造函数和其他成员。然后对照上面列出的几点,找出下面实现存在的问题。

template<typename T>
class MyUniquePtr
{
public:
    MyUniquePtr(T* ptr = nullptr)
        :mPtr(ptr)
    {}

    ~MyUniquePtr()
    {
        if(mPtr)
            delete mPtr;
    }

    MyUniquePtr(MyUniquePtr &&p) ;
    MyUniquePtr& operator=(MyUniquePtr &&p) ;

    MyUniquePtr(const MyUniquePtr &p) = delete;
    MyUniquePtr& operator=(const MyUniquePtr &p) = delete;

    T& operator*() const {return *mPtr;}
    T* operator->()const {return mPtr;}

    void reset(T* q = nullptr)
    {
        if(q != mPtr){
            if(mPtr)
                delete mPtr;
            mPtr = q;
        }
    }

    T* release()
    {
        T* res = mPtr;
        mPtr = nullptr;
        return res;
    }
    T* get() const {return mPtr;}
    void swap(MyUniquePtr &p)
    {
        using std::swap;
        swap(mPtr, p.mPtr);
    }
private:
    T* mPtr;
};

template<typename T>
MyUniquePtr<T>& MyUniquePtr<T>::operator=(MyUniquePtr &&p)
{
    if(*this != p)
    {
        if(mPtr)
            delete mPtr;
        mPtr = p.mPtr;
        p.mPtr = NULL;
    }
    return *this;
}

template<typename T>
MyUniquePtr<T> :: MyUniquePtr(MyUniquePtr &&p) : mPtr(p.mPtr)
{
    p.mPtr == NULL;
}

问题1:异常——缺失noexcept关键字

上述代码中的移动构造函数、移动赋值操作符以及部分接口函数的声明和定义中,缺失了noexcept关键字,该关键字能够告知标准库,本函数不会产生异常,如果未标记noexcept,标准库将产生一个额外的操作,增加了开销。

问题2:缺失bool值转换功能

观察IO类、unique_ptr、shared_ptr等资源管理相关的类,它们一般都会重新实现:

explicit operation bool() const noexcept

以便能直接将对象放置于条件语句中进行判断。

int main()
{
    std::unique_ptr<int> ptr(new int(42));

    if (ptr) std::cout << "before reset, ptr is: " << *ptr << '\n';
    ptr.reset();
    if (ptr) std::cout<< "after reset, ptr is: " << *ptr << '\n';
}

explicit关键字不能少,否则可能出现隐式转换,导致如下问题:

std::unique_ptr<int> p1(new int(13));
std::unique_ptr<int> p2(new int(14));

if(p1 == p2)
{
    //p1 p2 都会被转换为bool值,都为true,因此结果是两者相等。
    std::cout << "p1 is equal p2" << endl;
}

//输出: p1 is equal p2

这时p1==p2将返回true
编译器为了使两个对象能够互相比较,就会把他们都转换为bool型,从而导致错误。


问题3:隐式的类类型转换

如果构造函数只接受一个实参,则它实际上定义了转换此类类型的隐式转换机制,有时我们把这种构造函数称为转换构造函数(converting constructor)

是否允许隐式的类类型转换,是由类的语义和上下文环境决定的。
同样的,使用explicit可以抑制这种转换。也由此,unique_ptr只能进行直接初始化。


4.移动构造、移动赋值及自赋值问题

unique_ptr不可拷贝,但是可以移动。这是它的重要特征,因此,绝对不能忘记自定义移动构造函数和移动赋值运算符。
而重新实现移动赋值运算符时,要记得考虑自赋值问题。上述代码考虑了该问题。

优化:使用swap函数来实现operator=(移动赋值运算符),巧妙解决自赋值问题。且相比判断*this == p而言,提高了效率。

template<typename T>
MyUniquePtr<T>& MyUniquePtr<T>::operator=(MyUniquePtr &&p) noexcept
{
//交换本对象和右值p的内容
    this->swap(*this, p);//p.mPtr现在指向本对象曾经指向的内存
    return *this; //返回后,右值p将被销毁,从而指向delete p.mPtr
}

以下英文内容摘自Copy-and-Swap Idiom in C++

为什么*this == p版本不好:

1.自赋值检查: 自赋值情况的发生频率并不高,而无论何种情况都做自赋值检查,更加耗时。

为什么swap版本更好:

  1. 无需再做自赋值检查。由于自赋值检查发生频率很低,因此,所有在自赋值情况下复制相等的指针,其所浪费的时间并不多。
  1. 代码复用。复用了swap函数,精简了代码.
  1. 使用交换技术,能够延后针对unique_ptr内置指针成员的delete操作。这意味着临时的unique_ptr超出作用域之前,是潜在的可再次使用的。当其超出作用域后,unique_ptr的析构函数会将其正确销毁。

5.编写模板类

接下来的考察点,就是编写模板类了。没有太多可赘述的。


修改后的版本:

template<typename T>
class MyUniquePtr
{
public:
   explicit MyUniquePtr(T* ptr = nullptr)
        :mPtr(ptr)
    {}

    ~MyUniquePtr()
    {
        if(mPtr)
            delete mPtr;
    }

    MyUniquePtr(MyUniquePtr &&p) noexcept;
    MyUniquePtr& operator=(MyUniquePtr &&p) noexcept;

    MyUniquePtr(const MyUniquePtr &p) = delete;
    MyUniquePtr& operator=(const MyUniquePtr &p) = delete;

    T& operator*() const noexcept {return *mPtr;}
    T* operator->()const noexcept {return mPtr;}
    explicit operator bool() const noexcept{return mPtr;}

    void reset(T* q = nullptr) noexcept
    {
        if(q != mPtr){
            if(mPtr)
                delete mPtr;
            mPtr = q;
        }
    }

    T* release() noexcept
    {
        T* res = mPtr;
        mPtr = nullptr;
        return res;
    }
    T* get() const noexcept {return mPtr;}
    void swap(MyUniquePtr &p) noexcept
    {
        using std::swap;
        swap(mPtr, p.mPtr);
    }
private:
    T* mPtr;
};

template<typename T>
MyUniquePtr<T>& MyUniquePtr<T>::operator=(MyUniquePtr &&p) noexcept
{
    swap(*this, p);
    return *this;
}

template<typename T>
MyUniquePtr<T> :: MyUniquePtr(MyUniquePtr &&p) noexcept : mPtr(p.mPtr)
{
    p.mPtr == NULL;
}

相信在面试中,写一个涵盖以上知识点的unique_ptr应该足够了。
欢迎指出代码中的问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容