简体   繁体   中英

Using placement new with an std::function doesn't work

This code crashes at (*function)() . I'm running Visual Studio 2019 and compiling C++17 for Windows 10 x86_64. I've tested it on Linux with GCC (-std=c++17) and it works fine. I'm wondering if this is a problem with Visual Studio's C++ compiler or something that I'm not seeing.

#include <vector>
#include <array>
#include <functional>
#include <iostream>

int main() {
  const size_t blockSize = sizeof(std::function<void()>);
  using block = std::array<char, blockSize>;
  std::vector<block> blocks;

  auto lambda = [](){
    std::cout << "The lambda was successfully called.\n";
  };

  blocks.emplace_back(); 
  new (&blocks[0]) std::function<void()>(lambda);

  blocks.emplace_back(); 
  new (&blocks[1]) std::function<void()>(lambda);

  std::function<void()> *function = (std::function<void()> *)blocks[0].data();

  (*function)();

  return 0;
}

The error is a read access violation in the std::function internals.

When you append an element to a std::vector and that element would make the vector's size greater than its capacity the vector has to allocate new storage and copy/move all of its elements to the new space. While you've constructed complex std::function objects in the char arrays held in your vector, the vector doesn't know that. The underlying bytes that make up the std::function object will get copied to the vector's new storage, but the std::function 's copy/move constructor won't get called.

The best solution would be to just use a std::vector<std::function<void()>> directly. If you have to use a vector of blocks of raw storage for some reason then you'll need to pre-allocate space before you start inserting elements. ie

int main() {
  const size_t blockSize = sizeof(std::function<void()>);
  using block = std::array<char, blockSize>;
  std::vector<block> blocks;

  auto lambda = [](){
    std::cout << "The lambda was successfully called.\n";
  };

  blocks.reserve(2);  // pre-allocate space for 2 blocks

  blocks.emplace_back(); 
  new (&blocks[0]) std::function<void()>(lambda);

  blocks.emplace_back(); 
  new (&blocks[1]) std::function<void()>(lambda);

  std::function<void()> *function = (std::function<void()> *)blocks[0].data();

  (*function)();

  return 0;
}

Live Demo

Alternatively you could use a different data structure such as std::list that maintains stable addresses when elements are added or removed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM