简单包装类与智能指针

Posted

技术标签:

【中文标题】简单包装类与智能指针【英文标题】:Simple wrapper class vs. smart pointer 【发布时间】:2017-02-02 19:45:53 【问题描述】:

出于教育目的,我今天早些时候实现了一个包装类,定义如下(摘自一本书):

#ifndef WRAPPER_H
#define WRAPPER_H

template<class T>
class Wrapper

public:
  Wrapper()
   dataPtr = 0; 

  Wrapper(const T& inner)
  
    dataPtr = inner.clone();
  

  Wrapper(const Wrapper<T> &original)
  
    if (original.dataPtr != 0)
      dataPtr = original.dataPtr->clone();
    else
      dataPtr = 0;
  

  Wrapper &operator =(const Wrapper<T> &original)
  
    if (this != &original)
    
        if (dataPtr != 0)
          delete dataPtr;

        dataPtr = (original.dataPtr !=0) ? original.dataPtr->clone() : 0;
    
    return *this;
  

  ~Wrapper()
  
    if (dataPtr != 0)
      delete dataPtr;
  

  T &operator*()
  
    return *dataPtr;
  

  const T&operator*() const
  
    return *dataPtr;
  

  T *operator->()
  
    return dataPtr;
  

  const T * const operator->() const
  
    return dataPtr;
  
private:
  T *dataPtr;
;

#endif

主要思想是充当指针,并具有处理内存管理、复制构造函数、析构函数和赋值运算符的额外优势。它包装了具有克隆方法的类:它们返回一个指向自身副本的指针(而不是自身,指向使用newClass(*this) 制作的新副本)。

在某些方面它看起来像unique_ptr,因为被包装的对象只能通过这个包装器访问。但是,有一个区别,这就是我的问题所在。在这个包装类中,有一个构造函数,它通过接受对其包装的类的对象的引用来定义(上面代码中的第一个构造函数)。

这很方便。假设我们有类ABB 的构造函数引用了Wrapper&lt; A &gt;。然后我可以用另一个对象A构造一个对象B

A object1;
B object2(A);

这是因为object2是使用前面提到的Wrapper构造函数来构造一个Wrapper&lt; A &gt;(然后传递给B的构造函数)。

是否可以使用std::memory 中的任何智能指针来执行此操作?我的主要目标是教育,但实际上我不想重新发明***。

【问题讨论】:

复制构造函数不可用于唯一指针,因为它们在复制后将不再唯一。相反,共享指针是可复制的,其中副本只是强制执行它们共享​​>指向对象的所有权这一事实。 这看起来像是在做深拷贝,而不是共享/移动指针。 看起来像一个 intrusive_ptr 你应该使用nullptr而不是0,并且析构函数中的空检查是没有意义的。 我认为检查是不必要的。我从未测试过空指针的尖锐程度,但顾名思义它是尖的。 【参考方案1】:

智能指针旨在提供所有权语义,可以对其进行分类(并且与可用的 c++ 标准实现一起使用):

唯一所有权总是在传递时转移 共享没有单一的所有者,智能指针计算引用,如果这些引用下降到 0 则死亡 一个依赖指针,但提供了检查被引用指针是否仍然有效的能力

这与您的包装器实现完全不同。

【讨论】:

【参考方案2】:

是的,所有这些都是可能的......并且供参考......并且因为一旦我也实现了类似的东西......(也用于教育目的)......我可以分享我为智能指针制作的代码,引用计数......这意味着您可以根据需要创建任意数量的副本,当最后一个副本被销毁时,它将删除该对象

#ifndef UberPointer
#define UberPointer UPointer

template <class UClass> class UPointer

private:
    struct UPointerRef
    
        UClass* pObject;
        int _nCount;
        UPointerRef(UClass* pRef)_nCount=0;pObject = pRef;
        ~UPointerRef()if(pObject)delete pObject;
        void AddRef(void)++_nCount;
        void RemoveRef(void)if (--_nCount <= 0)delete this;
    ;
    UPointerRef* _pRef;

public:
    UPointer()
    
        _pRef = new UPointerRef(0x0);
        _pRef->AddRef();
    
    UPointer(UClass* pPointer)
    
        _pRef = new UPointerRef(pPointer);
        _pRef->AddRef();
    
    UPointer(UPointer<UClass> &oPointer)
    
        _pRef = oPointer._pRef;
        _pRef->AddRef();
    
    ~UPointer(void)
    
        _pRef->RemoveRef();
    
    UClass* GetObject()
    
        ASSERT(_pRef->pObject);
        return _pRef->pObject;
    
    operator UClass*(void)
    
        ASSERT(_pRef->pObject);
        return _pRef->pObject;
    
    UClass& operator*(void)
    
        ASSERT(_pRef->pObject);
        return *(_pRef->pObject);
    
    UClass* operator->(void)
    
        ASSERT(_pRef->pObject);
        return (_pRef->pObject);
    
    UPointer& operator=(UPointer<UClass> &oPointer)
    
        _pRef->RemoveRef();
        _pRef = oPointer._pRef;
        _pRef->AddRef();
        return *this;
    
    UPointer& operator=(UClass* pPointer)
    
        _pRef->RemoveRef();
        _pRef = new UPointerRef(pPointer);
        _pRef->AddRef();
        return *this;
    
    bool operator==(UClass* pPointer)
    
        return _pRef->pObject == pPointer;
    
    bool operator!=(UClass* pPointer)
    
        return _pRef->pObject != pPointer;
    
    bool operator !(void)
    
        return (_pRef->pObject == 0x0);
    
    operator bool(void)
    
        return (_pRef->pObject != 0x0);
    
;
#endif

【讨论】:

看起来你并没有违反规则,但无论如何都要把它放在这里:What are the rules about using an underscore in a C++ identifier? 是的..这些年来我开始开发自己的符号..不是c ++标准..但是可以跨语言使用..所以我在不得不使用时总是使用相同的符号在同一个项目中更改语言..(C#/javascript/SQL)..它减少了每次移动时的适应压力.. 命名约定是件好事。我使用很多前缀和后缀作为助记符和提示。下划线的担心是您可能会不小心使用标准库实现所拥有的名称并导致编译器错误或非常奇怪的运行时结果。【参考方案3】:
A object1;
B object2(A);

是否可以使用 std::memory 中的任何智能指针来执行此操作?

使用标准智能指针,您不会获得深拷贝语义。您将具有浅拷贝语义(使用 std::shared_ptr)或移动语义(使用 std::unique_ptr)。但是,没有什么能阻止您在您的类中创建一个返回智能指针的clone() 方法。这样一来,您就可以在需要时获得深层副本,同时仍然受益于智能指针带来的所有权语义。

【讨论】:

以上是关于简单包装类与智能指针的主要内容,如果未能解决你的问题,请参考以下文章

智能指针可以选择性地隐藏或重定向函数调用到它们包装的对象吗?

将这个包装在智能指针中的问题传递给 C++ 中的方法

C的智能指针/安全内存管理?

将包装在智能指针中的问题传递给C ++中的方法的问题

C ++在boost python中使用带有命名空间的自定义智能指针

智能指针的原理和简单实现