在具有许多自定义堆分配器的环境中,通常是否需要将原始void*
的地址缓存在自定义智能指针中?
稍后识别某个内存块Allo
的分配器(content
): -
分配块时,我认为有必要在它附近存储一个hacky信息(meta-data
和Allo*
)。
当我想要取消分配void*
时,我可以减去void*
指针以找到Allo*
,例如
template<class T>class StrongPointer{
public: void* content;
public: ~StrongPointer(){
Allo* allo=static_cast<Allo*>((void*)(static_cast<char*>(content)-4));
//^ 32-bit system
allo->deallocate(content);
}
public: void get(){ return static_cast<T*>(content); }
}
它应该有用。
然而,当我希望它支持施放StrongPointer<Derived>
- &gt;时,这件事就会破裂。 StrongPointer<Base2>
。
(根据C++ virtual table layout of MI(multiple inheritance))
class Base1{/*some fields */};
class Base2{/*some fields */};
class Derived : public Base1,public Base2{};
例如,将StrongPointer<Derived>
投射到StrongPointer<Base2>
的结果将StrongPointer<Base2>::content
不会直接 Allo*
的位置。
template<class T1,class T2> StrongPointer<T2> cast(StrongPointer<T1>&& t1){
StrongPointer<T2> r;
r.content=static_cast<T2*>(t1.get());
//^ location change, so "content-(4 bytes)" doesn't point to Allo* anymore
return r;
}
在我看来,有一些解决方法: -
Allo*
存储在每个强指针中。 OR Allo* + 1
,反映已分配内容的真实地址这一切归结为: -
我真的必须在Strong_Pointer<T>
内存储另一个变量吗?
答案 0 :(得分:3)
这里有一个人如何组织这个而不用侵入性而且不必使用像shared_ptr
这样的胖指针(如果胖指针可以使用shared_ptr
,我假设你想避免它们)。此示例不使用自定义智能指针,只需任何类型的指针即可。你唯一需要记住的是使用&#34; make&#34;只要你想使用自定义分配器就可以运行。
#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <memory>
#include <cstring>
struct Alloc;
struct UnalignedControlBlock
{
int magic1;
Alloc* allocator;
std::size_t size;
char magic2[19];
};
union ControlBlock
{
UnalignedControlBlock ucb;
std::max_align_t aligner;
};
struct Alloc
{
static Alloc global_allocator;
static void* allocate(std::size_t size)
{
void* p = ::operator new (size + sizeof(ControlBlock));
ControlBlock* cb = static_cast<ControlBlock*>(p);
cb->ucb.allocator = &global_allocator;
cb->ucb.magic1 = 42;
cb->ucb.size = size;
std::strcpy(cb->ucb.magic2, "Hey there!");
std::cout << "Allocate: block: " << cb << " size: " << size << " magic1: " << cb->ucb.magic1 << " magic2: " << cb->ucb.magic2 << " allocator: " << cb->ucb.alloca
return cb+1;
}
static void deallocate (void* p)
{
ControlBlock* cb = static_cast<ControlBlock*>(p);
cb--;
std::cout << "Deallocate: block: " << cb << " size: " << cb->ucb.size << " magic1: " << cb->ucb.magic1 << " magic2: " << cb->ucb.magic2 << " allocator: " << cb->u
::operator delete (cb);
}
};
template <class T>
struct Allocated : T
{
template <class ... Arg>
Allocated(Arg ... arg) : T(arg ...) {}
void* operator new (size_t size) { return Alloc::allocate(size); }
void operator delete (void* p) { return Alloc :: deallocate(p); }
};
template <class T, class ... Args>
std::unique_ptr<T> make_smart (Args ... args)
{
return std::unique_ptr<T>(new Allocated<T>(args...));
};
//试驾
struct Test1
{
const int filler = 42;
virtual ~Test1() {
std::cout << "Test1::~Test1 " << this << " " << filler << std::endl;
}
};
struct Test2 : virtual Test1
{
const int filler = 43;
virtual ~Test2() {
std::cout << "Test2::~Test2 " << this << " " << filler << std::endl;
}
};
struct Test3 : virtual Test1
{
const int filler = 44;
virtual ~Test3() {
std::cout << "Test3::~Test3 " << this << " " << filler << std::endl;
}
};
struct Test4 : Test2, Test3
{
const int filler = 45;
virtual ~Test4() {
std::cout << "Test4::~Test4 " << this << " " << filler << std::endl;
}
};
Alloc Alloc::global_allocator;
int main ()
{
std::unique_ptr<Test1> p1 = make_smart<Test1>();
std::unique_ptr<Test1> p2 = make_smart<Test2>();
std::unique_ptr<Test1> p3 = make_smart<Test3>();
std::unique_ptr<Test1> p4 = make_smart<Test4>();
}
测试输出
Allocate: block: 0x1817c20 size: 16 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Allocate: block: 0x1818080 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Allocate: block: 0x18180e0 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Allocate: block: 0x1818140 size: 48 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Test4::~Test4 0x1818170 45 Test3::~Test3 0x1818180 44 Test2::~Test2 0x1818170 43 Test1::~Test1 0x1818190 42 Deallocate: block: 0x1818140 size: 48 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Test3::~Test3 0x1818110 44 Test1::~Test1 0x1818120 42 Deallocate: block: 0x18180e0 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Test2::~Test2 0x18180b0 43 Test1::~Test1 0x18180c0 42 Deallocate: block: 0x1818080 size: 32 magic1: 42 magic2: Hey there! allocator: 0x6052e9 Test1::~Test1 0x1817c50 42 Deallocate: block: 0x1817c20 size: 16 magic1: 42 magic2: Hey there! allocator: 0x6052e9
这说明了从为继承调整的指针中正确恢复元数据。如果析构函数是虚拟的,则不需要自定义删除器。
答案 1 :(得分:2)
您应该使用标准shared_ptr。当您创建shared_ptr的新实例时,您可以定义将被调用以删除该内存的deleter function(or object)。
另一个简洁的特性是当你转换为shared_ptr<void>
时保留了deleter类,因此shared_ptr机制也可以从void指针中正确处理删除。
您可以按类型设置模板化删除器,但这只是实现细微差别。
这里有一个删除器的例子(只是为了好玩)完全覆盖了删除并且根本没有删除对象。
#include <iostream>
#include <memory>
class Myclass
{
public:
~Myclass()
{
std::cout << "Myclass Destructor" << std::endl;
}
};
struct MyDeleter {
void operator()(Myclass* p) const {
std::cout << "In custom destructor" << std::endl;
//delete p;
}
};
int main()
{
{
std::shared_ptr<Myclass> example1(new Myclass(), MyDeleter());
std::cout << "Delete void example1 at end of scope, with no actual deletion" << std::endl;
}
{
std::shared_ptr<Myclass> example2 = std::make_shared<Myclass>();
std::cout << "Delete example2 at end of scope" << std::endl;
}
{
std::shared_ptr<void> example3 = std::static_pointer_cast<void>(std::make_shared<Myclass>());
std::cout << "Delete void example3 at end of scope" << std::endl;
}
return 0;
}
输出:
Delete void example1 at end of scope, with no actual deletion
In custom destructor
Delete example2 at end of scope
Myclass Destructor
Delete void example3 at end of scope
Myclass Destructor
因此,如果你坚持重新发明轮子,你可以看一下精心设计的轮子的shared_ptr实现。