【问题标题】:Is it well defined behaviour to save a destructor adress of an object and call it later?保存对象的析构函数地址并稍后调用它是明确定义的行为吗?
【发布时间】:2017-04-18 23:26:52
【问题描述】:

我目前正在研究一个用 C++ 编写的堆栈分配器。应该可以通过模板方法从这个堆栈分配器中获取对象。一旦调用了另一个释放方法,这些对象就应该被销毁。在尝试不同的方法来实现析构函数的调用时,我偶然发现了以下内容:

auto destructor = someObject->~SomeClass;
destructor();

析构函数似乎被实际调用了,但是这对我来说有点奇怪。我有以下问题:

  1. 这是明确定义的行为吗?
  2. 析构函数的类型是什么(我可以用什么替换 auto 关键字)?
  3. 是否可以将多个不同类型对象的所有析构函数地址保存在一个列表中,然后再调用它们?

【问题讨论】:

  • 绝对不安全。如果你不小心调用了两次析构函数会发生什么?
  • 是的,我知道这很危险。如果通过在调用后删除地址来明确定义危险,我会解决危险。
  • 什么编译器允许这样做?我尝试时 g++ 和 clang++ 都会抱怨。
  • 您可能正在寻找deferred_heap。它带有一个免费的CppCon talk
  • 格式不正确,请参阅§[class.dtor]/p2:The address of a destructor shall not be taken。这基本上是未定义的行为,但我会提交错误报告,最好得到诊断。

标签: c++ c++11 memory memory-management


【解决方案1】:

正如 cmets 所指出的,这是未定义的行为/编译器扩展。

不过,你可以做你想做的事:

template<typename T>
std::function<void(T*)> defer_dtor()
{
   return [](T* ptr) {ptr->~T();};
}

或直接(更像你的问题,但独特的 lambda 类型不能直接添加到列表中。)

auto destructor = [&]{someObject->~SomeClass();};
destructor();

注意:这跟随您的问题,并且不调用delete ptr。这是否合适取决于上下文,但如果你想包装delete ptr,你可能会使用std::shared_ptr

[编辑] 我在您的问题中遗漏了一点点:“将多个对象的所有析构函数地址不同类型保存在一个列表中”。

这将是一个问题。确切地说,是一个设计问题。这些类型有什么关系?如果通过继承,则不需要析构函数列表,而是需要 virtual 析构函数。如果类型不相关,如何匹配对象和保存的析构函数?如果你有所有这些对象的类型,为什么不直接调用-&gt;~Type()

【讨论】:

  • 有可能。看我的回答。您可以将 lambda 转换为函数指针,并且该 lambda 可以是模板函数的一部分。
  • @DavidHaim:假设您不仅拥有对象 type,而且还拥有可用的实际对象本身。但在这种情况下,为什么不将对象本身绑定到 lambda 中呢?您的课程看起来像是 [object]{object-&gt;~T();} 的手卷等价物。
  • 性能。在 lambda 中捕获上下文可防止将其转换为函数指针。它需要存储在std::function 中,它可能使用虚函数+ 函数对象比单个void(*)(void*) 更胖。通过添加几行代码,您可以极大地优化您的分配器。你可能会喊“过早的优化”,但如果你已经花时间编写堆栈分配器而不是过早 - 你已经在优化了。
  • @DavidHaim:由于虚函数通常以函数指针表的形式实现,我认为两者大致等价。是的,捕获意味着它不能被强制转换为函数指针,但这正是因为它内化了你保持外部的状态(void* object)。当然,这种基本等价是由于我们都用一个间接层解决了这个问题。我只是押注于标准结构的优化优于手动等效结构。
  • 不过我同意,可以将GenericDesrtuctor 类调整为仅使用std::unique_ptr
【解决方案2】:

我可以保存几个不同对象的所有析构函数地址吗 输入一个列表并稍后调用它们?

是的。请注意,我的解决方案还保留了指向被破坏对象的指针,而不仅仅是析构函数。 你可以做一些 lambda 魔法来实现这一点 + 一些低级帮助:

class GenericDestructor {

private:    
    const void* object;
    void(*destructor)(const void*);

public:

    template<class T>
    GenericDestructor(const T& _object) noexcept :
        object(std::addressof(_object)){

        destructor = [](const void* _object) {
            auto original = static_cast<const T*>(_object);
            original->~T();
        };

    };

    void operator () () noexcept {
        destructor(object);
    }

};

存储:

std::vector<GenericDestructor> storedDestructors;
auto needsToBeDeleted = new std::string();
storedDestructors.emplace_back(*needsToBeDeleted);

销毁所有存储的对象:

for(auto& storedDestructor : storedDestructors) storedDestructor();

【讨论】:

  • 或者使用这个 lambda 作为 std::shared_ptr&lt;void*&gt;::shared_ptr(T* ptr, Deleter) 的第二个参数。
  • 可能。但是 OP 应该考虑到这个解决方案对现在对象 si 引用计数的奖金有更多的开销。
猜你喜欢
  • 2020-09-25
  • 1970-01-01
  • 2019-06-18
  • 2011-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-20
相关资源
最近更新 更多