使用类型擦除删除C++11中的void*

Delete void* in C++11 using type erasure

本文关键字:中的 void C++11 删除 类型 擦除      更新时间:2023-10-16

我正试图修复此类中的双重免费或损坏

struct Holder
{
    template <typename T>
    Holder(const T& v)
    {
        _v = new T{};
        memcpy(_v, &v, sizeof(T));
        _deleter = [this]{
            if (_v != nullptr)
            {
                delete reinterpret_cast<T*>(_v);
                _v = nullptr;
            }
        };
    }
    template <typename T>
    T get()
    {
        T t;
        memcpy(&t, _v, sizeof(T));
        return t;
    }
    ~Holder()
    {
        std::cout << "~Holder() " << std::endl;
        _deleter();
    }
private:
    void* _v;
    std::function<void()> _deleter;
};

这个类的目标是保持一个特定类型的值,比如boost::any。因此,我试图理解安全地释放所有内存的机制。

可能是这行代码:

delete reinterpret_cast<T*>(_v);

没有达到我的预期。。。

***建议后****

我已经使用注释建议重写了代码,并添加了移动构造函数

struct Holder
{
    template <typename T>
    Holder(const T& v)
    {
        std::cerr << "create " << N << std::endl;
        _v = new T(v);
        _deleter = [this]{
            if (_v != nullptr)
            {
                std::cerr << "deleter " << N << std::endl;
                delete reinterpret_cast<T*>(_v);
                _v = nullptr;
            }
        };
    }

    Holder(Holder&& rs)
    {
        _v = rs._v;
        _deleter = std::move(rs._deleter);
        rs._deleter = []{}; //usefull to avoid a bad function call
    }

    template <typename T>
    T get() const
    {
        return *reinterpret_cast<T*>(_v);
    }
    ~Holder()
    {
        //std::cout << "~Holder() " << N << std::endl;
        _deleter();
    }
private:
    void* _v;
    std::function<void()> _deleter;
};

现在看来是可行的,但我必须管理其他角落的情况:)可能最好的解决方案是使用boost::any:

struct Holder
{
    template <typename T>
    Holder(const T& v)
    {
        _v = v;
    }
    template <typename T>
    T get()
    {
        return boost::any_cast<T>(_v);
    }
private:
    boost::any _v;
};

但我正在努力了解没有它它它是如何工作的。

这是我的最后一个版本:

struct Holder
{
    template <typename T>
    Holder(const T& v)
    {
        std::cerr << "create " << N << std::endl;
        _v = new T(v);
        _deleter = [](void* ptr){
            if (ptr != nullptr)
            {
                std::cerr << "deleter " << std::endl;
                delete reinterpret_cast<T*>(ptr);
            }
        };
        _builder = [](void* &dest, void* src){
            dest = new T(*reinterpret_cast<T*>(src));
        };
    }
    Holder(const Holder& rs)
    {
        std::cerr << "copy constr" << std::endl;
        if (this != &rs)
        {
            rs._builder(_v, rs._v);
            _deleter = rs._deleter;
            _builder = rs._builder;
        }
    }
    Holder(Holder&& rs)
    {
        std::cerr << "move constr" << std::endl;
        if (this != &rs)
        {
            _v = rs._v;
            _deleter = std::move(rs._deleter);
            _builder = std::move(rs._builder);
            rs._deleter = [](void*){};
        }
    }
    Holder& operator=(const Holder& rs)
    {
        std::cerr << "copy operator" << std::endl;
        if (this != &rs)
        {
            rs._builder(_v, rs._v);
            _deleter = rs._deleter;
            _builder = rs._builder;
        }
        return *this;
    }
    Holder& operator=(Holder&& rs)
    {
        std::cerr << "move operator" << std::endl;
        if (this != &rs)
        {
            _v = rs._v;
            _deleter = std::move(rs._deleter);
            _builder = std::move(rs._builder);
            rs._deleter = [](void*){};
        }
        return *this;
    }
    template <typename T>
    T get() const
    {
        return *reinterpret_cast<T*>(_v);
    }
    ~Holder()
    {
        //std::cout << "~Holder() " << N << std::endl;
        _deleter(_v);
    }
private:
    void* _v;
    std::function<void(void* ptr)> _deleter;
    std::function<void(void* &, void* src)> _builder;
};

不要重新实现马。

using pvoid_holder = std::unique_ptr<void, std::function<void(void*)>>
template<class T>
pvoid_holder pvoid_it( T* t ) {
  return { t, [](void* v){ if (v) delete static_cast<T*>(v); } };
}

现在将pvoid_holder存储在Holder类中。它将为你处理一生的记忆。

您可以使用裸pvoid_holder,但它可能具有比您想要的更丰富的接口(例如,它将允许在不更改deleter的情况下更改存储的指针)。

您也可以将std::function替换为void(*)(void*)以获得边际性能增益。

这里有一个随机的想法。不过我还是不喜欢。这个设计背后的整个想法很糟糕。

template <typename T>
struct Holder
{
  public:
    Holder(T const& v)
    {
      new (&m_v) T(v);
    }
    T const& get() const
    {
      return reinterpret_cast<T const&>(m_v);
    }
    T& get()
    {
      return reinterpret_cast<T&>(m_v);
    }
    ~Holder()
    {
      std::cout << "~Holder() " << std::endl;
      get().~T();
    }
  private:
    char m_v[sizeof(T)];
};

这个类不再做与您的相同的事情,即它不能在std::vector<Holder>中存储任意类型,而只能存储相同的类型(std::vector<Holder<Foo>>)。注释太小,无法包含此代码,我想为您正在使用的内容显示一个更好看的语法;)。

话虽如此,你能做你想做的事情的唯一方法是添加第二层用于参考计数。也就是说,用类似于shared_ptr的东西替换void* _v,但当计数为零时,它不会调用delete,而是调用deleter(因此应该存储在这个新类中)。事实上,您的类看起来基本上像这个新类,只是您应该使它不可复制并提供引用计数(即通过boost::intrusive_ptr)。那么Holder可以是可复制的封装器。

可能这行代码:delete reinterpret_cast<T*>(_v);没有达到我的预期

不完全是。您的类型可能使用默认的复制ctor;这将复制您的数据指针_v和您的deleter。因此,当两个对象都进行析构函数时,两个deleter都会触发,导致数据被删除两次。(附带说明--不应该以_开头命名变量;这些标识符是为实现保留的)。

以下是正确执行类型擦除所需的步骤,假设我没有错误。更好的方法是坚持使用boost::any。

#include <utility>
struct EmptyType {}; // Thrown if unexpectedly empty
struct InvalidType {}; // Thrown if Holder(T) but get<U>.
struct Holder
{
   Holder()
      : data_()
      , deleter_(e_deleter)
      , copier_(e_copier)
      , typetag_()
   {
   }
   template<typename T>
      Holder(const T& t)
         : data_(erase_cast(new T))
         , deleter_(deleter<T>)
         // Need to explicitly carry T's copy behavior
         // because Holder's default copy ctor isn't going to
         , copier_(copier<T>)
         // You need some way to protect against getting
         // an Orange out of a Holder that holds an Apple.
         , typetag_(id<T>())
      {
      }
   Holder(const Holder& rhs)
      : data_(rhs.copy())
      , deleter_(rhs.deleter_)
      , copier_(rhs.copier_)
      , typetag_(rhs.typetag_)
   {
   }
   template<typename T>
      T get()
      {
         if (!data_) throw EmptyType();
         T rv(fetch<T>());
         return rv;
      }
   Holder(Holder&& rhs)
      : data_()
      , copier_(rhs.copier_)
      , deleter_(rhs.deleter_)
      , typetag_(rhs.typetag_)
   {
      std::swap(data_, rhs.data_);
   }
   ~Holder()
   {
      destroy();
   }
private:
   // Reinterpret_cast wrappers labeled semantically
   template<typename T>
      static void* erase_cast(T* t) { return reinterpret_cast<void*>(t); }
   template<typename T>
      static T* unerase_cast(void* t) { return reinterpret_cast<T*>(t); }
   // Return a data copy
   void* copy() const { return copier_(data_); }
   // Return const reference to data
   template<typename T>
      const T& fetch() {
         if (typetag_!=id<T>()) throw InvalidType();
         return *unerase_cast<T>(data_);
      }
   // Destroy data
   void destroy() { deleter_(data_); data_=0; }
   // ==== Type erased copy semantics ===
   void*(*copier_)(void*);
   template<typename T>
      static void* copier(void* v) {
         return erase_cast<T>(new T(*unerase_cast<T>(v)));
      }
   static void* e_copier(void*) { return 0; }
   // ==== Type erased delete semantics ===
   void(*deleter_)(void*);
   template<typename T>
      static void deleter(void* v) {
         delete unerase_cast<T>(v);
      }
   static void e_deleter(void*) {}
   // ==== Type protection using tagging (could also use typeid)
   static int makenewid() { static int i=0; return i++;}
   template<typename T>
      static int id() { static int i=makenewid(); return i; }
   // Type erased data
   void* data_;
   // Type erased tag
   int typetag_;
};

这里有一些测试/演示代码:

#include <iostream>
#include <vector>
#define FAIL() std::cout << "Fail" << std::endl; return 1
int foos=0;
struct Foo { Foo(){++foos;} Foo(const Foo&){++foos;} ~Foo(){--foos;} };
int bars=0;
struct Bar { Bar(){++bars;} Bar(const Bar&){++bars;} ~Bar(){--bars;} };
int main() {
   {
      std::vector<Holder> v;
      Foo fx,fy,fz; Bar ba,bb;
      v.push_back(fx); v.push_back(fy); v.push_back(fz);
      v.push_back(ba); v.push_back(ba); v.push_back(bb);
      v.push_back(Holder());
      try {
         Foo y = v[2].get<Foo>();
      }
      catch (EmptyType&) { FAIL(); }
      catch (InvalidType&) { FAIL(); }
      try {
         Foo y = v[4].get<Foo>();
         FAIL();
      }
      catch (EmptyType&) { FAIL(); }
      catch (InvalidType&) { }
      try {
         Foo y = v[6].get<Foo>();
         FAIL();
      }
      catch (EmptyType&) {  }
      catch (InvalidType&) { FAIL(); }
   }
   if (foos||bars) { FAIL(); }
   std::cout << "Pass" << std::endl;
}

测试结果:

$ ./a.exe
Pass