简单包装类 vs 智能指针

3

为了教育目的,我今天早些时候编写了一个包装类,如下所示(这是从一本书中摘录的):

#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

主要思想是充当指针,并具有处理内存管理、复制构造函数、析构函数和赋值运算符的额外优势。它包装了具有克隆方法的类:它们返回指向自己的副本的指针(而不是指向自己,而是使用"new" "Class(*this)"创造新的副本)。
在某些方面,它似乎像一个"unique_ptr",因为封装的对象只能通过此包装器访问。然而,有一个区别,这就是我的问题所在。在这个包装类中,定义了一个构造函数,接受包装类引用对象的引用(上面代码中的第一个构造函数)。
这非常方便。假设我们有类"A"和类"B",并且类"B"的构造函数需要引用"Wrapper< A >"。那么我可以使用另一个类"A"来构造一个对象"B"。
A object1;
B object2(A);

这是因为使用object2构造了一个Wrapper< A >(然后将其传递给B的构造函数),使用了前面提到的Wrapper构造函数。
std::memory中的智能指针中是否有任何一种可以实现这个目标?我的主要目标是教育,但在实践中我不想重复造轮子。

2
无法为unique pointers提供拷贝构造函数,因为在复制后它们将不再是唯一的。Shared pointers可以进行复制,复制只是强制它们共享所指对象的所有权。 - skypjack
1
这看起来像是在进行深拷贝,而不是共享/移动指针。 - user4581301
1
看起来像是intrusive_ptr。 - Mikel F
3
在析构函数中进行空指针检查是没有意义的,你应该使用nullptr而不是0 - Jesper Juhl
1
我认为这个检查是不必要的。我从未测试过空指针有多锋利,但它的名称暗示它很尖。 - user4581301
显示剩余2条评论
3个回答

6

智能指针 旨在提供 所有权 语义,可以根据可用的 C++ 标准实现进行分类:

  • 独占 所有权总是在传递时转移
  • 共享 没有单个所有者,智能指针计算引用计数,如果引用计数降至 0,则销毁所管理对象
  • 依赖性指针,但具有检查所指向的指针是否仍然有效的功能

这与您的包装器实现非常不同。


1

是的,所有这些都是可能的。作为参考,并且因为我曾经也实现过类似的东西(也是出于教育目的),我可以分享我为智能指针编写的代码,它具有引用计数功能,这意味着您可以创建任意多个副本,当最后一个副本被销毁时,它将删除对象。

#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

1
看起来你没有违反规则,但我还是会把这个放在这里:C++标识符中使用下划线的规则是什么? - user4581301
是的,多年来我开始开发了自己独特的表示法。它不是C++标准,但可以跨语言使用。因此,在同一个项目中切换语言时,我总是使用相同的表示法(如C#、JavaScript和SQL),这样可以减少每次切换时的适应压力。 - CaldasGSM
命名规范是一件好事。我使用很多前缀和后缀作为助记符和提示。下划线的问题在于,您可能会意外使用标准库实现拥有的名称,并获得编译器错误或非常奇怪的运行时结果。 - user4581301

1
A object1;
B object2(A);

使用std::memory中的任何智能指针都可以实现这个吗?

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接