【问题标题】:C++ dynamic loading of classes: Why is a "destroy" function needed?C++ 动态加载类:为什么需要“销毁”函数?
【发布时间】:2017-02-27 18:43:16
【问题描述】:

This page 检查并给出了一个非常清楚的例子来说明如何动态加载和使用一个类,不过有些东西我很难理解:

我明白为什么需要“创建”功能,但为什么需要“销毁”功能?为什么没有将接口析构函数声明为足够纯虚拟?

我做了一个相同的例子,除了:

~polygon() = 0;

triangle 的析构函数是:

triangle::~triangle() {
    std::cout << "triangle Dtor is called" <<std::endl;
}

那么当我使用时:

delete poly;

确实显示了消息(linux下的GCC 5.4.0)。

我试图寻找其他示例,但他们都提到并使用了“销毁”功能,没有使用简单的纯虚拟析构函数的示例,这让人相信我在这里遗漏了一些东西,所以..它是什么?

不想使用destroy函数的背景是我想在shared_ptr中使用分配的对象而不关心它的生命周期,使用“destroy”函数会很棘手,因此我需要知道是否有必要。

【问题讨论】:

  • “使用“销毁”功能会很棘手” - 为什么?如果这很棘手,那么您可能做错了什么。
  • 您确定要专门从动态对象加载一个类?不只是创建这个类的实例吗? c++ 中的dynamic_memorydlopen 彼此无关(只是想确保我了解您想要做什么)
  • @Hayt "c++ 中的dynamic_memory 和dlopen 没有关系" 但是"内存管理" 和"shared_ptr" 是有关系的吧?
  • 无论谁投票关闭此作为Question about pure virtual destructor 的副本,这不是远程副本。这涉及到虚拟析构函数,但它是在问为什么它们对于跨二进制 delete 来说是不够的,而不是为什么首先需要它们。

标签: c++ memory-management dlopen dynamic-loading


【解决方案1】:

the same link 中进一步阅读:

您必须同时提供创建和销毁功能;您不能在可执行文件中使用 delete 来销毁实例,而应始终将其传递回模块。这是因为在 C++ 中,操作符 new 和 delete 可能会被重载;这将导致调用不匹配的 new 和 delete,这可能会导致任何事情,从无到有内存泄漏和分段错误。如果使用不同的标准库来链接模块和可执行文件,也是如此。

这里的关键字是new and delete may be overloaded,因此在调用者的代码中与在共享对象的代码中做一些不同的事情,如果你在二进制文件中使用delete,它将调用析构函数并释放根据共享对象中的析构函数的内存,但这可能不是共享对象中删除运算符的行为,可能共享对象中的new 没有分配任何内存,因此您可能会遇到分段错误,并且可能new 所做的不仅仅是为该对象分配内存,并且通过在共享对象中不调用匹配的delete 存在泄漏,共享对象和二进制文件之间也有可能进行不同的堆处理。

无论如何,shared_ptr 可以通过调用自定义删除器的 lambda 函数相当容易地获得自定义删除器;确实,shared_ptr 不能在其模板参数中包含删除器,这有点烦人,但您可以编写一个简单的包装器,以使其更简单/更简洁,以便在所有位置使用一致的删除器创建它(目前没有可用的编译器,请原谅任何错别字):

shared_ptr<triangle> make_shared_triangle(triangle *t) {
    return std::shared_ptr<triangle>(t, [](triangle *t) { destroy_triangle(t); });
}

【讨论】:

  • 是的,但是怎么做?我的意思是,如果polygon 在二进制文件或共享对象中编译和链接,并且二进制文件和triangle 共享对象都使用相同的声明/实现,那么基于解析@ 的vtable 无论如何都不会在那里进行释放分配987654332@析构函数?
  • @MysticOdin:所描述的场景是可执行文件显式动态加载函数以创建(和销毁)它没有实现的对象,该对象可能有不同的delete。考虑一个包含一对new/delete 的二进制文件,以及一个与另一个共享对象(我们称之为newweird/deleteweird 的SO)。 SO 中的工厂函数调用newweird。如果你只是让二进制调用delete中的shared_ptr,它没有使用deleteweird;你有一个不匹配(分配 w/newweird,删除 w/delete)可能会破坏堆或程序段错误。
  • 基本上,delete 做了两件事: 1. 为实例调用析构函数(可以通过 vtable 找到;我认为这很好) 2. 通过operator delete 释放内存(可以有共享对象中完全不同的定义,要么是由于被替换,要么是因为共享对象链接了不同版本的标准库)。 #2 对原始二进制文件根本不可见。
  • 好的我明白释放基本上粘在析构函数的末尾(如果它有实现,否则它实际上放在前面),基本上你说的是这个错误并且分配/解除分配可能会被重载和奇怪地实现,解析 vtable 中的析构函数并不能解决解除分配,而只是调用析构函数并且解除分配是通过二进制而不是共享对象完成的,这是正确的吗?
  • @MysticOdin:没错。
【解决方案2】:

不想使用destroy函数的背景是我想在shared_ptr中使用分配的对象而不关心它的生命周期,使用“destroy”函数会很棘手,因此我需要知道是否这是必要的。

然后您需要使用显式的Deletercreate your shared_ptr(参见构造函数的表格4。向下滚动到示例)。

template< class Y, class Deleter > shared_ptr( Y* ptr, Deleter d );

类似:

shared_ptr<polygon> sh_ptr_val(
                     my_triangle, 
                     [](auto ptr) { destroy_triangle(ptr); }
                   );

[编辑以解决 Hayt 的第一条评论]

struct triangle_factory { 
  static shared_ptr<triangle> create() {
    shared_ptr<polygon> ret(
                         create_triangle(), 
                         [](auto ptr) { destroy_triangle(ptr); }
                       );
    return std::move( ret )
  }; 
private: 
   static create_t* create_triangle; 
   static destroy_t* destroy_triangle; 
}

create_t* triangle_factory::create_triangle=(create_t*) dlsym(triangle, "create");
destroy_t* triangle_factory::destroy_triangle=(destroy_t*) dlsym(triangle, "destroy");

【讨论】:

  • 如果destory_triangle 不是全局的,但您将无法调用它。
  • @Hayt 我怀疑 shared_ptr 中的三角形创建/打包将发生在工厂函数/方法中。如果在那个地方知道destroy_triangle就足够了。
  • 问题中链接了一个示例,显示了函数的实际来源。 destroy_triangledlsym(...)获取的函数指针
  • @Hayt ...并且同一个地方还使用 dlsym(...) 获取的函数指针创建三角形。所以 struct triangle_factory { static shared_ptr&lt;triangle&gt; create() { /* do the stuff */}; private: static create_t* create_triangle; static destroy_t* destroy_triangle; } 应该足够“全局”。
【解决方案3】:

如果你真的想通过你链接到的例子,你可以使用一个自定义函数,当智能指针应该删除它的对象时使用。

std::shared_ptr<class> object(create_object(), //create pointer
[=](class* ptr)
{
    destroy_object(ptr);
});

当共享指针应该删除自身时,将调用 lambda 而不是 delete

注意:我将函数指针复制到 lambda 中的 destroy_object 函数([=] 将执行此操作)。只要在动态加载上下文中使用时不调用dlclose(),这应该是有效的。当您使用dlclose 时,虽然这会导致错误。

【讨论】:

  • 我想你的意思是destroy_object(ptr);
  • 为什么要为删除器 lambda 复制每个范围内的元素?这似乎可能非常浪费......如果任何可见的内容不可复制,则无效。
  • @xaxxon 是的,这只是我懒惰。我只用所需的函数指针纠正了这个问题。
  • @xaxxon:在实践中,[=] will only capture the stuff it uses。显式捕获可能对阅读代码的人更友好一些,但实际上在某些方面更容易出错;如果你命名了所有的捕获,但后来没有使用其中的一些,编译器仍然必须捕获命名的捕获;如果你让它隐含,编译器总是捕获它需要的东西,不多也不少。
  • @ShadowRanger 哇不知道。但对 lambdas 是有意义的。
猜你喜欢
  • 2011-08-29
  • 2013-09-18
  • 2018-05-02
  • 2011-08-30
  • 2010-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-13
相关资源
最近更新 更多