Search code examples
c++c++17visual-studio-2019std-functionplacement-new

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.


Solution

  • 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. i.e.

    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.