And*_*hko 10 c++ memory-management c++11
简单的多继承
struct A {};
struct B {};
struct C : A, B {};
Run Code Online (Sandbox Code Playgroud)
或虚拟继承
struct B {};
struct C : virtual B {};
Run Code Online (Sandbox Code Playgroud)
请注意类型不是多态的.
自定义内存分配:
template <typedef T, typename... Args>
T* custom_new(Args&& args...)
{
void* ptr = custom_malloc(sizeof(T));
return new(ptr) T(std::forward<Args>(args)...);
}
template <typedef T>
void custom_delete(T* obj)
{
if (!obj)
return obj;
void* ptr = get_allocated_ptr(obj); // here
assert(std::is_polymorphic_v<T> || ptr == obj);
obj->~T();
custom_free(ptr); // heap corruption if assert ^^ failed
}
B* b = custom_new<C>(); // b != address of allocated memory
custom_delete(b); // UB
Run Code Online (Sandbox Code Playgroud)
如何实现get_allocated_ptr非多态类型?对于多态类型dynamic_cast<void*>的工作.
或者,我可以检查这obj是一个指向基类的指针,因为通过指向基类的指针删除非多态对象是UB.我不知道该怎么做或者根本不可能.
operator delete在这种情况下(例如VC++)正确释放内存,尽管标准说它是UB.它是如何做到的?编译器特有的功能?
实际上你遇到的问题比获取完整对象的地址更严重.考虑这个例子:
struct Base
{
std::string a;
};
struct Derived : Base
{
std::string b;
};
Base* p = custom_new<Derived>();
custom_delete(p);
Run Code Online (Sandbox Code Playgroud)
在此示例中,custom_delete实际上将释放正确的address(static_cast<void*>(static_cast<Derived*>(p)) == static_cast<void*>(p)),但该行将obj->~T()调用析构函数Base,意味着该b字段已泄露.
而不是返回原始指针custom_new,返回绑定到类型T的对象,并知道如何删除它.例如:
template <class T> struct CustomDeleter
{
void operator()(T* object) const
{
object->~T();
custom_free(object);
}
};
template <typename T> using CustomPtr = std::unique_ptr<T, CustomDeleter<T>>;
template <typename T, typename... Args> CustomPtr<T> custom_new(Args&&... args)
{
void* ptr = custom_malloc(sizeof(T));
try
{
return CustomPtr<T>{ new(ptr) T(std::forward<Args>(args)...) };
}
catch (...)
{
custom_free(ptr);
throw;
}
}
Run Code Online (Sandbox Code Playgroud)
现在不可能意外地释放错误的地址并调用错误的析构函数,因为调用custom_free的唯一代码知道它正在删除的事物的完整类型.
注意:注意unique_ptr :: reset(指针)方法.使用自定义删除器时,此方法非常危险,因为调用程序上的onus提供以正确方式分配的指针.如果使用无效指针调用该方法,编译器将无法帮助.
可能是您希望将基本指针传递给函数并赋予该函数释放对象的责任.在这种情况下,您需要使用类型擦除来隐藏消费者对象的类型,同时在内部保留其最派生类型的知识.最简单的方法是使用std::shared_ptr.例如:
struct Base
{
int a;
};
struct Derived : Base
{
int b;
};
CustomPtr<Derived> unique_derived = custom_new<Derived>();
std::shared_ptr<Base> shared_base = std::shared_ptr<Derived>{ std::move(unique_derived) };
Run Code Online (Sandbox Code Playgroud)
现在你可以随意传递shared_base,当最终引用被释放时,整个Derived对象将被销毁并传递给它的正确地址custom_free.如果你不喜欢shared_ptr它的语义,那么创建一个带unique_ptr语义的类型擦除指针是相当简单的.
注意:这种方法的一个缺点是shared_ptr需要为其控制块(不使用custom_malloc)单独分配.通过更多的工作,你可以解决这个问题.你需要创建一个包装自定义分配器custom_malloc和custom_free再使用std::allocate_shared,以创建您的对象.
#include <memory>
#include <iostream>
void* custom_malloc(size_t size)
{
void* mem = ::operator new(size);
std::cout << "allocated object at " << mem << std::endl;
return mem;
}
void custom_free(void* mem)
{
std::cout << "freeing memory at " << mem << std::endl;
::operator delete(mem);
}
template <class T> struct CustomDeleter
{
void operator()(T* object) const
{
object->~T();
custom_free(object);
}
};
template <typename T> using CustomPtr = std::unique_ptr<T, CustomDeleter<T>>;
template <typename T, typename... Args> CustomPtr<T> custom_new(Args&&... args)
{
void* ptr = custom_malloc(sizeof(T));
try
{
return CustomPtr<T>{ new(ptr) T(std::forward<Args>(args)...) };
}
catch (...)
{
custom_free(ptr);
throw;
}
}
struct Base
{
int a;
~Base()
{
std::cout << "destroying Base" << std::endl;
}
};
struct Derived : Base
{
int b;
~Derived()
{
std::cout << "detroying Derived" << std::endl;
}
};
int main()
{
// Since custom_new has returned a unique_ptr with a deleter bound to the
// type Derived, we cannot accidentally free the wrong thing.
CustomPtr<Derived> unique_derived = custom_new<Derived>();
// If we want to get a pointer to the base class while retaining the ability
// to correctly delete the object, we can use type erasure. std::shared_ptr
// will do the trick, but it's easy enough to write a similar class without
// the sharing semantics.
std::shared_ptr<Base> shared_base = std::shared_ptr<Derived>{ std::move(unique_derived) };
// Notice that when we release the shared_base pointer, we destroy the complete
// object.
shared_base.reset();
}
Run Code Online (Sandbox Code Playgroud)
您只能使用dynamic_cast静态类型来做到这一点,并且静态类型T必须是多态的。否则看看这段代码:
struct A { int a; };
struct B { int b; };
struct C : A, B {};
B *b1 = new C, *b2 = new B;
Run Code Online (Sandbox Code Playgroud)
如果您尝试通过指向 B 的指针进行删除,则无法知道b1或 是否b2需要调整为get_allocated_ptr。无论如何,您需要B具有多态性才能获取指向大多数派生对象的指针。
| 归档时间: |
|
| 查看次数: |
374 次 |
| 最近记录: |