首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >这种制造异类函子容器的技术能被打捞出来吗?

这种制造异类函子容器的技术能被打捞出来吗?
EN

Stack Overflow用户
提问于 2015-08-28 21:07:48
回答 1查看 248关注 0票数 1

这个博客帖子描述了一种创建异构指针容器的技术。基本技巧是创建一个简单的基类(即没有显式函数声明,没有数据成员,没有)和一个模板化的派生类来存储具有任意签名的std::function<>对象,然后使容器将unique_ptr保存到基类的对象。代码也是可在GitHub上使用。

我不认为这段代码是健壮的;std::function<>可以从lambda创建,其中可能包含一个捕获,其中可能包含一个非平凡对象的值副本,必须调用该对象的析构函数。当Func_t类型在从映射中删除时被unique_ptr删除时,只会调用它的(琐碎)析构函数,因此std::function<>对象永远不会被正确删除。

我已经将GitHub上示例中的用例代码替换为“非平凡类型”,然后由lambda中的值捕获并添加到容器中。在下面的代码中,从示例中复制的部分在注释中得到了注意;其他的都是我的。对于这个问题,可能有一个更简单的演示,但我正在努力从这个问题中获得一个有效的编译。

代码语言:javascript
复制
#include <map>
#include <memory>
#include <functional>
#include <typeindex>
#include <iostream>

// COPIED FROM https://plus.google.com/+WisolCh/posts/eDZMGb7PN6T
namespace {

  // The base type that is stored in the collection.
  struct Func_t {};
  // The map that stores the callbacks.
  using callbacks_t = std::map<std::type_index, std::unique_ptr<Func_t>>;
  callbacks_t callbacks;

  // The derived type that represents a callback.
  template<typename ...A>
    struct Cb_t : public Func_t {
      using cb = std::function<void(A...)>;
      cb callback;
      Cb_t(cb p_callback) : callback(p_callback) {}
    };

  // Wrapper function to call the callback stored at the given index with the
  // passed argument.
  template<typename ...A>
    void call(std::type_index index, A&& ... args)
    {
      using func_t = Cb_t<A...>;
      using cb_t = std::function<void(A...)>;
      const Func_t& base = *callbacks[index];
      const cb_t& fun = static_cast<const func_t&>(base).callback;
      fun(std::forward<A>(args)...);
    }

} // end anonymous namespace

// END COPIED CODE

class NontrivialType
{
  public:
    NontrivialType(void)
    {
      std::cout << "NontrivialType{void}" << std::endl;
    }

    NontrivialType(const NontrivialType&)
    {
      std::cout << "NontrivialType{const NontrivialType&}" << std::endl;
    }

    NontrivialType(NontrivialType&&)
    {
      std::cout << "NontrivialType{NontrivialType&&}" << std::endl;
    }


    ~NontrivialType(void)
    {
      std::cout << "Calling the destructor for a NontrivialType!" << std::endl;
    }

    void printSomething(void) const
    {
      std::cout << "Calling NontrivialType::printSomething()!" << std::endl;
    }
};

// COPIED WITH MODIFICATIONS
int main()
{
  // Define our functions.
  using func1 = Cb_t<>;

  NontrivialType nt;
  std::unique_ptr<func1> f1 = std::make_unique<func1>(
      [nt](void)
      {
        nt.printSomething();
      }
  );

  // Add to the map.
  std::type_index index1(typeid(f1));
  callbacks.insert(callbacks_t::value_type(index1, std::move(f1)));

  // Call the callbacks.
  call(index1);

  return 0;
}

这将产生以下输出(使用G++ 5.1而不进行优化):

代码语言:javascript
复制
NontrivialType{void}
NontrivialType{const NontrivialType&}
NontrivialType{NontrivialType&&}
NontrivialType{NontrivialType&&}
NontrivialType{const NontrivialType&}
Calling the destructor for a NontrivialType!
Calling the destructor for a NontrivialType!
Calling the destructor for a NontrivialType!
Calling NontrivialType::printSomething()!
Calling the destructor for a NontrivialType!

我计算五个构造函数调用和四个析构函数调用。我认为这表明我的分析是正确的--容器不能正确地破坏它拥有的实例。

这种方法可以解决吗?当我向Func_t添加一个虚拟=default析构函数时,我会看到一个匹配的ctor/dtor调用数:

代码语言:javascript
复制
NontrivialType{void}
NontrivialType{const NontrivialType&}
NontrivialType{NontrivialType&&}
NontrivialType{NontrivialType&&}
NontrivialType{const NontrivialType&}
Calling the destructor for a NontrivialType!
Calling the destructor for a NontrivialType!
Calling the destructor for a NontrivialType!
Calling NontrivialType::printSomething()!
Calling the destructor for a NontrivialType!
Calling the destructor for a NontrivialType!

..。所以我认为这个改变可能就足够了。是吗?

(注意:这种方法的正确性-或不足-独立于异构函数容器的概念是否是一个好主意。在一些非常具体的情况下,可能有一些优点,例如,在设计解释器时;例如,可以将Python类看作是一个包含异构函数的容器,再加上一个包含异构数据类型的容器。但总的来说,我提出这个问题的决定--,而不是--表明在很多情况下这可能是个好主意。)

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2015-08-28 21:31:23

这基本上是一个人试图实现类型删除,并使它可怕的错误。

是的,你需要一个虚拟的破坏者。被删除的东西的动态类型显然不是Func_t,所以如果析构函数不是虚拟的,它显然是UB。

不管怎么说,整个设计都被彻底破坏了。

类型擦除的意义在于,您有一组共享共同特性的不同类型(例如,“可以用int调用并获得一个double”),并且希望将它们变成具有该特性的单个类型(例如,std::function<double(int)>)。从本质上说,类型擦除是一条单向的街道:一旦你抹去了它,你就无法在不知道它是什么的情况下把它拿回来。

把一些东西抹去到一个空类意味着什么?除了“这是件事”之外,什么都没有。它是一个std::add_pointer_t<std::common_type_t<std::enable_if_t<true>, std::void_t<int>>> (更常见的称为void*),在模板服装中被混淆。

这个设计还有很多其他的问题。因为该类型被删除为虚无,因此它必须恢复原始类型,以便对其进行任何有用的操作。但是,在不知道原始类型是什么的情况下,您无法恢复原始类型,因此它最终使用传递给Call的参数类型来推断存储在映射中的东西的类型。这是非常容易出错的,因为A...代表传递给Call的参数类型和值类别,很难完全匹配std::function模板参数的参数类型。例如,如果您在其中存储了一个std::function<void(int)>,并且尝试用一个int x = 0; Call(/* ... */ , x);调用它,那么它就是未定义的行为。

更糟糕的是,任何不匹配都隐藏在static_cast后面,这会导致未定义的行为,从而使查找和修复变得更加困难。还有一个奇怪的设计,它要求用户在“知道”类型是什么的时候传递一个type_index,但是与这段代码的所有其他问题相比,它只是一个旁白。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32279797

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档