【问题标题】:Why can't we trivially copy std::function为什么我们不能简单地复制 std::function
【发布时间】:2016-12-20 17:09:01
【问题描述】:

我问这个的原因是我需要将std::function存储在一个向量中,而我们公司内部的向量基本上是在做realloc,如果它需要更多的内存。 (基本上只是memcpy,不涉及复制/移动运算符)

这意味着我们可以放入容器中的所有元素都必须是可简单复制的。

这里有一些代码来演示我有问题的副本:

void* func1Buffer = malloc(sizeof(std::function<void(int)>));
std::function<void(int)>* func1p = new (func1Buffer) std::function<void(int)>();
std::function<void(int)>* func2p = nullptr;
*func1p = [](int) {};
char func2Buffer[sizeof(*func1p)];
memcpy(&func2Buffer, func1p, sizeof(*func1p));
func2p = (std::function<void(int)>*)(func2Buffer);
// func2p is still valid here
(*func2p)(10);
free(func1Buffer);
// func2p is now invalid, even without std::function<void(int)> desctructor get triggered
(*func2p)(10);

我知道我们应该支持元素的复制/移动,以便安全地存储 std::function。 但是我还是很好奇上面无效的std::function复制的直接原因是什么。

------------------------------------------ --------UpdateLine----------------------------- ------------

更新了代码示例。

通过更多调试我们的内部向量,我找到了此失败的直接原因。

简单复制的std::function对原始对象内存有一定的依赖关系,删除原始内存会破坏复制不好的std::function,即使没有破坏原始对象。

感谢大家对本帖的回复。这都是有价值的输入。 :)

【问题讨论】:

  • 为什么我们不能简单地复制 std::function?因为标准是这样定义的。
  • 只要您使用非捕获 lambda,您就可以在向量中存储函数指针。
  • 这里有一些更新,我在本地犯了一些错误,我在问题中给出的代码示例实际上是有效的。
  • 实际上,这是因为函数对象包含指向自身其他部分的内部指针。 Memcpying 对象会使这些指针悬空。
  • @RaymondChen,是的,这个是有效的。在破坏 func1 之后使用 func2p 很容易崩溃。

标签: c++ c++11 std-function


【解决方案1】:

问题在于必须如何实现std::function:它必须管理它所持有的任何对象的生命周期。所以当你写的时候:

{
    std::function<Sig> f = X{};
} 

f 超出范围时,我们必须调用X 的析构函数。此外,std::function 将[潜在地] 分配内存来保存X,因此f 的析构函数也必须[潜在地] 释放该内存。

现在考虑当我们尝试这样做时会发生什么:

char buffer[100000]; // something big
{
    std::function<void()> f = X{};
    memcpy(buffer, &f, sizeof(f));
}
(*reinterpret_cast<std::function<void()>*>(buffer))();

在我们调用“存储”在buffer 的函数时,X 对象已经被销毁,并且保存它的内存已经[可能] 被释放。不管X 是否为TriviallyCopyable,我们都没有X。我们有艺术家以前称为X

因为std::function 有责任管理自己的对象,所以它不能是TriviallyCopyable 即使我们添加了它管理的所有可调用对象都是TriviallyCopyable 的要求。


要在您的realloc_vector 中工作,您需要像function_ref(或std::function&lt;&gt;*)这样的东西(即,一个根本不拥有任何资源的类型),或者您需要实现自己的版本在function 中,(a) 将自己的存储保留为成员以避免分配内存 (b) 只能使用TriviallyCopyable 可调用对象构造,因此它本身变得可以轻松复制。哪种解决方案更好取决于您的程序实际在做什么。

【讨论】:

    【解决方案2】:

    但是我还是很好奇无效的直接原因是什么 上面的 std::function 副本。

    std::function 不能是 TriviallyCopyable(或有条件的 TriviallyCopyable),因为作为通用可调用对象包装器,它不能假定存储的可调用对象是 TriviallyCopyable

    考虑实现您自己的 std::function 版本,该版本仅支持 TriviallyCopyable 可调用对象(使用固定缓冲区进行存储),或者在适用情况下使用函数指针向量。

    【讨论】:

    • 问题不在于存储的任何内容都可能无法轻松复制,而是必须在堆上分配该对象
    【解决方案3】:

    可平凡复制是与给定类型内在相关的东西,而不是与对象相关的东西。
    考虑以下示例:

    #include<type_traits>
    #include<functional>
    
    int main() {
        auto l = [](){};
        static_assert(not std::is_trivially_copyable<decltype(l)>::value, "!");
    
        std::function<void(void)> f;
        bool copyable = std::is_trivially_copyable<decltype(f)>::value;
        f = l;
    
        // do something based on the
        // fact that f is trivially copyable
    }
    

    一旦你将 lambda 分配给函数,你怎么能强制执行这个属性,这是不可轻易复制的?

    您正在寻找的是一种运行时机制,它可以根据分配给函数的实际对象做出决定。
    这不是std::is_trivially_copyable 的工作方式。
    因此,编译器必须在编译时就std::function 的给定特化做出决定。因为它是可调用对象的通用容器,您可以将其分配给普通可复制对象以及不可普通复制的对象,其余的不用多说。

    【讨论】:

      【解决方案4】:

      std::function 可能会为捕获的变量分配内存。与任何其他分配内存的类一样,它不是简单可复制的。

      【讨论】:

      • 不仅如此,空对象也可以是非TriviallyCopyable
      猜你喜欢
      • 1970-01-01
      • 2019-06-12
      • 2012-10-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-26
      • 2011-04-07
      相关资源
      最近更新 更多