std::promise 自定义分配器似乎使用全局新

std::promise with custom allocator appears to use global new

上下文:我正在编写一个库,它在许多 stdlib 数据结构中为想要自定义内存分配以实现实时性能的用户公开自定义分配器。

我想对 std::promisestd::future 使用自定义分配器。我的理解是,当分配器传递给 std::promise 时,其 future 对象也使用该自定义分配器。

我的测试覆盖全局新建和删除以跟踪调用默认运算符的次数。我还实现了一个使用 mallocfree 的自定义分配器,但使用不同的状态来计算 allocations/deallocations (在真实示例中,这将被替换为实时安全分配器)。

当我为 "large" 对象调用 std::promise::set_value 时,似乎调用了全局运算符 new 和 delete,即使 promise 是使用自定义分配器构造的。

这是一个基本示例。 (为简洁起见删除了分配器样板,您可以在 Gist 上看到完整的可编译版本:https://gist.github.com/jacquelinekay/a4a1a282108a55d545a9

struct Foo {
  std::vector<int, InstrumentedAllocator<int>> bar;
};

int main(int argc, char ** argv) {
  (void) argc;
  (void) argv;
  InstrumentedAllocator<void> alloc;

  std::promise<Foo> promise_(std::allocator_arg, alloc);
  std::shared_future<Foo> future_ = promise_.get_future().share();

  // Start a thread that blocks for a few ms and sets the future value
  std::thread result_thread(
    [&promise_]() {
      Foo result;
      result.bar.push_back(1);
      result.bar.push_back(2);
      result.bar.push_back(3);
      // test_init starts counting calls to global new/delete
      // (stored in variables global_runtime_allocs/deallocs)
      test_init = true;
      std::this_thread::sleep_for(std::chrono::milliseconds(5));
      promise_.set_value(result);
      test_init = false;
    }
  );
  future_.wait();
  result_thread.join();
  std::cout << "Runtime global allocations: " << global_runtime_allocs << " (expected: 0)" << std::endl;
  std::cout << "Runtime global deallocations: " << global_runtime_deallocs << " (expected: 0)" << std::endl;
}

此示例的全局运算符 new 还打印 "runtime" 分配的大小(来自 std::promise::set_value),导致此输出:

$ clang++ promise_allocator.cpp -std=c++11 -lpthread
$ ./a.out
Allocation size: 16
Runtime global allocations: 1 (expected: 0)
Runtime global deallocations: 1 (expected: 0)

我在 gcc 4.8 和 Clang 3.4 上得到了相同的结果。这是对标准的正确解释吗?我希望 set_value 使用 promise 的分配器。

结合调试器回溯和梳理 GCC 的 stdlib 实现,我已经弄清楚为什么会发生这种情况,尽管我自己没有解决方案或解决方法。

std::promise::set_value 调用其未来的内部函数,future::_M_set_result。 [1] 将函数对象__res传入此函数调用_Function_base的构造函数,可能是因为函数的签名没有通过引用传递__res_。 [2] _Function_base 的构造函数调用 _M_init_functor,如果函数正在使用本地存储,则它会执行一个新的放置或分配一个新对象。 [3] 由于某些我还没有确定的原因,future 内部使用的函数不使用本地存储,因此在构造函数中分配。

通过浏览我可以找到的标准工作草案 [4],该标准并未具体说明 promise 中分配的预期行为。但是,我无法控制 promise 内部使用的函数的分配行为,这很不方便,我可能会在 gcc 中提交一个关于它的错误。

  1. https://github.com/gcc-mirror/gcc/blob/gcc-4_8_4-release/libstdc%2B%2B-v3/include/std/future#L985
  2. https://github.com/gcc-mirror/gcc/blob/gcc-4_8_4-release/libstdc%2B%2B-v3/include/std/future#L352
  3. https://github.com/gcc-mirror/gcc/blob/gcc-4_8_4-release/libstdc%2B%2B-v3/include/std/functional#L1957
  4. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf

这似乎是 4.9 版本系列 libstdc++ 中的错误,已在版本 5 版本系列中修复。 运行 您在 version 5.1 或更高版本的 Wandbox 上的要点仅产生输出:

Runtime global allocations: 0 (expected: 0)
Runtime global deallocations: 0 (expected: 0)