这是我遇到过的一件有点奇怪的事情。这是一个项目的一部分,我正在制作一个竞技场分配器和智能指针,目标是将所有东西都包裹在过量的泡沫包装中,看看它最终会在哪里,只是为了个人体验。创建对象后立即调用析构函数 - 甚至在函数的 return 语句之前!不知何故,作用域要么小于创建它的函数,要么正在调用析构函数。我的
bound_ptr
静态创建器和分配器中返回 bound_ptr
的函数都遇到此问题。
我可能在提供问题信息和代码时遗漏了一些东西,现在是凌晨 1:30,我很累。我会在大约 8 小时后回来查看。
bound_ptr.hpp
#include <iostream>
template <typename T>
class bound_ptr
{
private:
T* ptr = nullptr;
size_t size = 0;
void (*deallocator)(void* mem, void* userdata) = nullptr;
void* userdata = nullptr;
bool managed = false;
public:
bound_ptr(const bound_ptr&) = delete;
bound_ptr& operator =(const bound_ptr&) = delete;
bound_ptr(bound_ptr&& other) noexcept
{
this->ptr = other.ptr;
this->size = other.size;
this->deallocator = other.deallocator;
this->userdata = other.userdata;
this->managed = other.managed;
other.ptr = nullptr;
other.size = 0;
other.deallocator = nullptr;
}
// The idea is to move the pointer into the class, so the class owns it explicitly
// Just a note. I tested removing the '&&' and the issue still persists
// element_count is used for arrays to check array bounds and prevent OOB accesses
// dealloc function ptr is potentially called in the bound_ptr destructor
// An extra pointer to be passed to the dealloc function when calling it
// If false, the provided dealloc function will be called when this is destroyed
bound_ptr(T*&& ptr, size_t element_count,
void (*dealloc)(void*, void*)=nullptr,
void* userdata=nullptr, bool managed=false) :
ptr(ptr), size(element_count), deallocator(dealloc), userdata(userdata), managed(managed)
{}
// A wrapper around delete to make it work for the custom destructor
static void _delete(void* mem, void* userdata) { delete (T*)mem; }
~bound_ptr()
{
if (this->ptr == nullptr)
return;
if (!this->managed)
this->ptr->~T();
if (this->deallocator != nullptr && this->ptr != nullptr)
this->deallocator(this->ptr, this->userdata);
}
// This function is the culprit.
// Should call the constructor of the type with the provided arguments
template <typename... C>
static bound_ptr<T> make_bound(C&&... args)
{
auto ptr = bound_ptr(
new T(std::forward<T>(args)...), 1, _delete, nullptr, true
);
// Destructor already called for T?! How and why?
std::cout << "PreReturn" << std::endl;
return ptr;
}
};
main.cpp
#include "bound_ptr.hpp"
struct A
{
int id;
A(int id) :
id(id)
{ std::cout << "Created " << id << std::endl; }
~A()
{ std::cout << "Destroyed " << this->id << std::endl; }
};
int main()
{
bound_ptr<A> a = bound_ptr<A>::make_bound(69);
}
预期输出为;
Created 69
PreReturn
Destroyed 69
但是实际输出是;
Created 69
Destroyed 69
PreReturn
custom_allocator.hpp
- 未经过编译测试
template<typename T, typename... C>
inline bound_ptr<T> Alloc::allocate_bound(size_t num_elements, C&&... ctor_args)
{
bound_ptr<T> a(
std::move(
(T*)this->allocate_bytes(num_elements * sizeof(T))
),
num_elements,
_bound_ptr_free, // A static function to call the custom allocator's free
this,
true
);
// No output at all
new (a.get()) T(std::forward<T>(ctor_args)...);
// Outputs "Created" and "Destroyed" before returning
return std::move(a);
}
当我使用 new 时,为什么以及如何调用析构函数?它应该位于堆上并且不是静态管理的,因此它永远不应该调用析构函数,除非我在放置新的情况下直接调用删除或析构函数。即使在调用析构函数之后,在类中的堆栈上分配的对象仍然有效,但这将导致任何使用堆的类出现问题。
一旦我删除了基本类型的复制和移动构造函数
T
,答案就出人意料地简单找到了——它是std::forward
。我错误地认为 std::forward
会将模板参数包解压到构造函数中,但意外地导致调用移动或复制构造函数。
只需更换
new T(std::forward<T>(ctor_args)...)
与
new T(ctor_args...)
当您从标准库中复制随机位而不了解它为何使用、它做什么或如何做时,就会发生这种情况。
谢谢 yksisarvinen、一些程序员兄弟以及其他人指出我的代码中的错误和问题。