首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >实现我自己的共享-所有权-双向智能指针

实现我自己的共享-所有权-双向智能指针
EN

Code Review用户
提问于 2016-11-19 15:28:26
回答 3查看 101关注 0票数 1

我正在实现自己的双向共享所有权智能指针,因为我需要一种方法来替换托管对象--一组智能指针指向一个新对象(这是下面代码中的reset_all方法)。

我试图尽可能接近std::shared_ptr的S界面,同时避免了我不理解/不需要的东西,比如从其他智能指针类型中进行混叠和转换。

代码语言:javascript
复制
//(it's all in an header file)
#pragma once

template <typename T>
class Manager {
    template <typename U>
    friend class SharedPtr;

    T* managed;
    unsigned int counter;

    Manager(T* new_ptr):
        managed(new_ptr), counter(1) {}
    ~Manager() = default;

    Manager<T>* own() {
        ++counter;
        return this;
    }
    void disown() {
        if (!(--counter)) {
            delete managed;
            delete this;
        }
    }
};

template <typename T>
class SharedPtr {
    public:
    SharedPtr() = default;
    SharedPtr(T* new_ptr) {
        if (new_ptr) {
            manager = new Manager<T>(new_ptr);
        }
    }
    SharedPtr(const SharedPtr &rhs):
        manager(rhs.manager->own()) {}
    SharedPtr(SharedPtr &&rhs):
        manager(rhs.manager) {
        rhs.manager = nullptr;
    }

    SharedPtr& operator=(const SharedPtr &rhs) {
        if (manager) {
            manager->disown();
        }
        manager = rhs.manager->own();
        return *this;
    }
    SharedPtr& operator=(SharedPtr &&rhs) {
        if (manager) {
            manager->disown();
        }
        manager = rhs.manager;
        rhs.manager = nullptr;
        return *this;
    }

    void swap(SharedPtr &rhs) {
        Manager<T>* tmp = manager;
        manager = rhs.manager;
        rhs.manager = tmp;
    }

    void reset() {
        if (manager) {
            manager->disown();
        }
        manager = nullptr;
    }
    void reset(T* new_ptr) {
        if (manager) {
            manager->disown();
        }
        manager = new Manager<T>(new_ptr);
    }

    T* get() {
        if (manager) {
            return manager->managed;
        }
        return nullptr;
    }

    T& operator*() {
        return *(manager->managed);
    }

    T* operator->() {
        return manager->managed;
    }

    unsigned int use_count() {
        if (manager) {
            return manager->counter;
        }
        return 0;
    }

    bool unique() {
        return use_count() == 1;
    }

    operator bool() {
        return (bool) manager;
    }

    void reset_all(T* new_ptr) {
        if (manager) {
            delete manager->managed;
            manager->managed = new_ptr;
            return;
        }
        manager = new Manager<T>(new_ptr);
    }

    ~SharedPtr() {
        if (manager) {
            manager->disown();
        }
    }

    private:
    Manager<T>* manager = nullptr;
};
EN

回答 3

Code Review用户

回答已采纳

发布于 2016-11-20 18:52:20

设计评论

您已经确定托管对象可能是null (当所管理的对象是null时)。

就我个人而言,我认为总是有一个托管对象。这将使您的其余代码编写起来更简单(我喜欢更简单的代码),因为永远不会有空对象。

当然,这个论点的另一个方面是,允许一个null托管对象可以更好地管理资源。我还没有做过计算--所以我还没有得出结论,但这可能是值得做的,并在代码中进行注释。

代码评审

命名空间

您应该将所有代码放在自己的命名空间中。这也有助于使您的包含警卫独特。

更喜欢包括警卫

并非所有编译器都支持此实用程序。

代码语言:javascript
复制
#pragma once

所以更喜欢使用一种没有变化的系统。

管理器

class Manager不是在class SharedPtr之外使用的,所以我个人认为它是这个SharedPtr的私有成员类。

尽管您不进行任何复制,但显然Manager是不可复制的。因此,让它明确地不可复制,以确保您不会意外地这样做,这将是一个好主意。

delete this;是非常危险的。

代码语言:javascript
复制
            delete this;

我宁愿更改接口,以便disown()的调用者对manager对象执行完全删除。

代码语言:javascript
复制
bool disown() {
    return ((--counter) == 0);
}
~Manager() {
    delete managed;
}

单参数构造器

当您有一个参数构造函数时,您必须非常小心。编译器将使用单个参数构造函数将一种类型急切地转换为另一种类型(在您最不期望的时候,这可能是一个问题)。

审查这一点:

代码语言:javascript
复制
actionOne(SharedPtr<int> const& val){/*Do Stuff*/}

int main()
{
    int b;
    actionOne(&b);  // Caboom.
}

在这里,编译器看到它将int*作为参数。但是它所拥有的函数的唯一版本需要SharedPtr<int> const,但这很容易通过直接构建SharedPtr<int>来实现,而且它将这样做。在这种情况下,这是非常危险的,因为指针没有被动态分配,而是将被删除。

因此,将单个参数构造函数显式化(特别是当它们获得所有权时)。

代码语言:javascript
复制
    explicit SharedPtr(T* new_ptr);

显式构造nullptr

随着nullptr的引入,我们有一个可以用于null值的对象。但是,我没有看到用nullptr覆盖显式构造的构造函数。

代码语言:javascript
复制
    explicit SharedPtr(nullptr_t);

移动语义.

将移动构造函数标记为noexcept并将赋值操作符移动是一个好主意。这是因为(因为它们通常不是在创建资源),但这也允许使用标准库容器进行一些良好的优化。

代码语言:javascript
复制
    SharedPtr(SharedPtr &&rhs) noexcept;
    SharedPtr& operator=(SharedPtr &&rhs) noexcept;
    void swap(SharedPtr &rhs) noexcept;

为什么重写std::

代码语言:javascript
复制
    void swap(SharedPtr &rhs) {
        Manager<T>* tmp = manager;
        manager = rhs.manager;
        rhs.manager = tmp;
    }

    void swap(SharedPtr &rhs) noexcept {std::swap(manager, rhs.manager);}

空对象怎么办?

代码语言:javascript
复制
    void reset(T* new_ptr) {
        if (manager) {
            manager->disown();
        }
        manager = new Manager<T>(new_ptr);
    }

在大多数情况下,nullptr会导致空管理器对象。但此重置将为空指针创建一个管理器对象。我会努力做到始终如一。

Const正确性

如果函数不改变对象的状态。那么它应该被标记为const。

代码语言:javascript
复制
    unsigned int use_count() const;
    bool unique() const;

显式bool铸

当前对bool的强制转换有点危险,因为编译器在尝试获取类型以匹配某些操作时将使用它。

考虑:

代码语言:javascript
复制
SharedPtr<int>   val1(new int(4));
SharedPtr<int>   val2(new int(8));

if (val1 == val2) {
    std::cout << "Val1 and Val2 match\n";  // Will print out.
}

这是因为编译器看到它可以通过将两个值转换为bool然后进行比较来进行比较。

您可以通过标记函数explicit来修复这个问题。

代码语言:javascript
复制
    explicit operator bool() const;

这只允许在布尔上下文中显式使用对象或显式转换为bool时将其转换为bool。

代码语言:javascript
复制
if (val1)   // This works as expected as the expression for an if
            // statement requires a boolean and thus it is considered
            // a boolean context and will allow the bool cast operator
            // to fire even if it is explicit.
{
    std::cout << "Val1 is null\n";
}

不喜欢这个,

您正在混合业务和资源逻辑。

代码语言:javascript
复制
    void reset_all(T* new_ptr) {
        if (manager) {
            delete manager->managed;
            manager->managed = new_ptr;
            return;
        }
        manager = new Manager<T>(new_ptr);
    }

如果用户希望将被管理的值设置为另一个值,则只需使用托管对象赋值操作符即可。

代码语言:javascript
复制
    SharedPtr<int>   x(new int(6));


    x.reset_all(new int(5));

    // or

    *x = 5;

插头给我.

我在这里详细描述了编写智能指针的所有问题:

独特ptr

共享ptr

构造函数

票数 1
EN

Code Review用户

发布于 2016-11-19 16:56:04

  1. 我不认为它是SharedPtr,因为与std::shared_ptr的对比太大,而名称太similar.可能是DoublePtr或类似的,以强调双重间接,这是关键的功能.
  2. 我会让Manager成为类的私有非模板成员,因为它是一个实现细节。另外,我会把它变成一个愚蠢的结构,并将它包含的有用函数(disown)放到DoublePtr的私有部分中。
  3. 虽然没有分配的默认ctor可能已经足够允许无资源状态(可能是这样),但是没有理由不允许创建指针组,它以nullptr.开始,因此我将删除ctor中的检查。
  4. op=错了,试试自我分配吧.它应该是不承认自己的。
  5. 作为一个简单的交换,op=(&&)会更好。
  6. op(bool)是错误的:使用get() != nullptr
  7. resetreset_all很容易成为从基本异常安全升级到强异常安全(提交或回滚),只需创建一个新的DoublePtr和交换即可。
  8. 您的许多函数可以而且应该标记为noexcept
  9. 此外,纯观察方法应标记为const
票数 1
EN

Code Review用户

发布于 2016-11-19 18:00:31

在操作符bool()中,我更喜欢显式的非空检查,如:

代码语言:javascript
复制
 operator bool() {
        return manager != nullptr;
    }

而不是抛出指针。其余的在我看来都很好。

票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/147517

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档