在动态分配时将C++11对象添加到列表中

Adding C++11 object to list when dynamically allocated?

本文关键字:添加 列表 对象 C++11 动态分配      更新时间:2023-10-16

假设我有一个类X:

struct X
{
   ...
};

我有一个全局向量V:

vector<X*> V;

我想将X的新实例添加到V,当且仅当它是动态分配的(作为一个完整的派生对象,而不是子对象(:

int main()
{
    X x; // not added to V
    new X; // added to V
    struct D : X {};
    new D; // not added to V
}

有办法做到这一点吗?也许是通过某种方式重载/覆盖operator new

struct X {
public:
    static void* operator new(std::size_t size) {
        void* p = ::operator new(size);
        if (size == sizeof(X))
            V.push_back(static_cast<X*>(p));
    }
    static void operator delete(void* p, std::size_t size) {
        if (size == sizeof(X))
            V.erase(std::remove(V.begin(), V.end(), p), V.end());
        ::operator delete(p, size);
    }
};

注意,有时V的元素指向还不是或不再实际是X的存储器。用户可以绕过这些功能,但他们必须尝试。

如果您有另一个继承X但大小相同的类(因此除了"空"基类之外没有其他子对象(,如struct Y : public X {};,则上面的代码会认为new Y正在分配X。如果这是一个问题,您还需要将operator newoperator void添加到每个此类Y中。我认为没有比这更通用的解决方案了。

基于aschepler的方法构建,但现在使用虚拟基类重定向D的构造函数调用(不向向量添加实例(。

主要思想是执行两步注册:首先,将对operator new的任何调用(无论是针对X还是派生类(注册到unordered_set(X::dyn_alloc_set(。然后,在构造X时,根据最派生的类型进行选择,如果this已被动态分配,则将其添加到V;如果它不是派生类,则添加

虚拟基类的构造函数必须从最派生的类型调用,因此在构造过程中可以使用它来区分DX

#include <unordered_set>
#include <typeinfo>
#include <vector>
#include <iostream>
#include <algorithm>
struct X;
std::vector<X*> V;
struct virt_base_class
{
    friend struct X;
private:
    virt_base_class(X* p);  // this can only and will only be called by X
public:
    virt_base_class()  // this will be called by any class derived from X
    {}
};
struct X
    : protected virtual virt_base_class
{
private:
    friend class virt_base_class;
    static std::unordered_set<X*> dyn_alloc_set;
    static bool dynamically_allocated(X* p)
    {
        return dyn_alloc_set.count(p) > 0;
    }    
public:
    X()
        : virt_base_class(this)
    {}
    static void* operator new(std::size_t size) {
        void* p = ::operator new(size);
        if (size == sizeof(X))
            dyn_alloc_set.insert( static_cast<X*>(p) );
        return p;
    }
    static void operator delete(void* p, std::size_t size) {
        if (size == sizeof(X))
        {
            dyn_alloc_set.erase( static_cast<X*>(p) );
            V.erase( std::remove(V.begin(), V.end(), static_cast<X*>(p)),
                     V.end() );
        }
        ::operator delete(p);
    }
};

virt_base_class::virt_base_class(X* p)
{
    if( X::dynamically_allocated(p) )
        V.push_back(p);
}
struct D : X
{};  // D::D will implicitly call virt_base_class::virt_base_class()

std::unordered_set<X*> X::dyn_alloc_set;

int main()
{
    X x;
    X* p = new X;
    D d;
    D* pd = new D;
    std::cout << V.size();
}

更新:使用thread_local存储以避免unordered_set:

struct X
    : protected virtual virt_base_class
{
private:
    friend class virt_base_class;
    static thread_local X* last_dyn_allocated;
    static bool dynamically_allocated(X* p)
    {
        return p == last_dyn_allocated;
    }
public:
    X()
        : virt_base_class(this)
    {}
    static void* operator new(std::size_t size) {
        void* p = ::operator new(size);
        if (size == sizeof(X))
        {
            last_dyn_allocated = static_cast<X*>(p);
        }
        return p;
    }
    static void operator delete(void* p, std::size_t size) {
        if (size == sizeof(X))
        {
            X* pp = static_cast<X*>(p);
            if(last_dyn_allocated == pp)
                last_dyn_allocated = nullptr;
            V.erase( std::remove(V.begin(), V.end(), pp),
                     V.end() );
        }
        ::operator delete(p);
    }
};
thread_local X* last_dyn_allocated = nullptr;

我认为最好的近似方法是强制使用动态分配对象的工厂:

#include <algorithm>
#include <utility>
#include <vector>
class X {
  static std::vector<X*> dynamic_xs;
  static void* operator new(std::size_t size) {
    return ::operator new(size);
  }
public:
  ~X() {
    auto end = std::end(dynamic_xs);
    auto pos = std::find(std::begin(dynamic_xs), end, this);
    if (pos != end) {
      if (pos != --end) {
        std::iter_swap(pos, end - 1);
      }
      dynamic_xs.pop_back();
    }
  }
  template <typename... Args>
  friend X* make_x(Args&&... args) {
    X* p = new X(std::forward<Args>(args)...);
    dynamic_xs.push_back(p);
    return p;
  }
};
std::vector<X*> X::dynamic_xs;

客户端可以实例化堆栈分配的X,但动态分配会导致访问错误,因为operator newprivate