如何在函数调用后初始化变量,就像 new 表达式提供的那样?

How to initialize a variable after a function call, like the new-expression provides?

目前我正在研究自定义内存分配,缺点之一是我必须编写多行才能获得与new-expression提供的相同结果只需一个简单的电话。

简单初始化:

MyClass *obj = new MyClass(3.14);

不太简单的初始化:

void *obj_mem = alloc->Allocate(sizeof MyClass, alignof(MyClass));
MyClass *obj = new(obj_mem) MyClass(3.14);

我将向我的项目组提供分配器,例如那个分配器,并希望他们实际使用它们,而不是退回到调用 new,因为我们需要这些更快的分配器来管理我们的记忆。

但要做到这一点,我将不得不设计出最简单的语法来使用我的自定义分配器初始化变量。


我的解决方案

我最好的选择是在每个 class 中覆盖 operator new,因为它是新表达式的 allocation function

class MyClass
{
    ...

    void* operator new(size_t size, Allocator *alloc)
    {
        return alloc->Allocate(size, alignof(MyClass));
    }
}

然后初始化变量的语法变成了我最终想要的:

MyClass *obj = new(alloc) MyClass(3.14);

但是,如果我能有一个与上述相同的一般等价物,那就太好了。所以我不必为每个 class.

覆盖 operator new

如果您对这种初始化形式感到满意:

MyClass *obj = new(alloc) MyClass(3.14);

也就是说,如果您可以将这样的对象传递给 new,那么您可以使用放置参数全局重载 new

void* operator new(std::size_t size, Allocator *alloc)
{
    return alloc->Allocate(size, alignof(std::max_align_t));
}

new(alloc) 的任何非数组使用都会调用此函数。您的大问题是没有匹配的 "placement delete" 语法。因此,要正确删除该对象,您需要执行您通常对通过放置新创建的对象的任何其他用途执行的操作:

obj->~Typename();
::operator delete(obj, sizeof(obj), alloc);

当然,这需要 operator delete 重载:

void operator delete(void *obj, std::size_t sz, Allocator *alloc)
{
    alloc->Deallocate(obj, sz, alignof(std::max_align_t));
}

如果您可以访问 C++17,则 operator newoperator delete 可以将对象的实际对齐方式作为参数。

你可以做一个工厂模板函数。素描:

template<class R,class... T>
R* create(alloc& alloc,T&&... t)
{
    void *obj_mem = alloc.Allocate(sizeof(R), alignof(R));
    R* obj = new(obj_mem) R(std::forward<T>(t)...);
    return obj:
}

如果您可以(并且想要)修改您的分配器,我会添加一个成员函数来分配和构造。在 class SomeAllocator:

template <typename T, typename... Args>
T* SomeAllocator::Construct(Args&&... args)
{
    auto ptr = Allocate(sizeof(T), alignof(T));
    return new (ptr) T(std::forward<Args>(args)...);
}

这样您就可以像

一样使用它
auto obj = alloc->Construct<MyClass>(3.14);

对于销毁和删除,您可以 - 为了提供统一的语法 - 添加 Destruct 方法:

template <typename T>
void SomeAllocator::Destruct(T*& t)
{
    t->~T();
    Deallocate(t, sizeof(T), alignof(T);
    t = nullptr;
}

完全杀死 new。无论如何,你必须将创造与破坏捆绑在一起。

template<class T>
struct destroy {
  Alloc* pool = nullptr;
  void operator()(T* t)const { 
    ASSERT(t);
    t->~T();
    ASSERT(alloc);
    alloc->Dealloc( t );
  }
};
template<class T>
using my_unique_ptr = std::unique_ptr<T, destroy<T>>;

namespace details{
  template<class T, class...Args>
  my_unique_ptr<T> my_make_unique( Alloc* alloc, Args&&...args ) {
    void* p_data = alloc->Allocate(sizeof(T), alignof(T));
    try {
      T* ret = ::new(p_data) T(std::forward<Args>(args)...);
      return {ret, destroy<T>{alloc}};
    } catch (...) {
      alloc->Dealloc( p_data );
      throw;
    }
  }
}
/// usual one:
template<class T, class...Args>
my_unique_ptr<T> my_make_unique( Alloc* alloc, Args&&...args ) {
  return details::my_make_unique<T>( alloc, std::forward<Args>(args)... );
}
// permit leading il:
template<class T, class U, class...Args>
my_unique_ptr<T> my_make_unique( Alloc* alloc, std::initializer_list<U> il, Args&&...args ) {
  return details::my_make_unique<T>( alloc, il, std::forward<Args>(args)... );
}
// for {} based construction:
template<class T>struct tag_t{using type=T;};
template<class T>using no_deduction=typename tag_t<T>::type;
template<class T>
my_unique_ptr<T> my_make_unique( Alloc* alloc, no_deduction<T>&&t ) {
  return details::my_make_unique<T>( alloc, std::move(t) );
}

现在 my_make_unique 接受一个 Alloc* 和构造参数,它 returns 一个绑定了销毁代码的智能指针。

此唯一指针可以隐式传递给 std::shared_ptr<T>(通过移动)。