Callbacks and Function Pointers

std::function vs Raw Function Pointers

Are there any limitations or drawbacks to using std::function instead of raw function pointers?

Abstract art representing computer programming

While std::function provides a more flexible and user-friendly interface compared to raw function pointers, it does come with some trade-offs. Let's explore the limitations and drawbacks of using std::function:

1. Performance Overhead

std::function typically has more overhead than a raw function pointer. This is because std::function is designed to be more flexible and can store different types of callable objects (functions, lambdas, functors), which requires some internal machinery.

Additionally, the compiler

#include <chrono>
#include <functional>
#include <iostream>

void SimpleFunction() {
  std::cout << "Working...\n";
}

int main() {
  using namespace std::chrono;
  const int iterations = 10000;

  // Raw function pointer
  void (*rawPtr)() = SimpleFunction;
  auto start = high_resolution_clock::now();

  for (int i = 0; i < iterations; ++i) {
    rawPtr();
  }
  auto end = high_resolution_clock::now();
  auto a = end - start;

  // std::function
  std::function funcObj = SimpleFunction;
  start = high_resolution_clock::now();

  for (int i = 0; i < iterations; ++i) {
    funcObj();
  }
  end = high_resolution_clock::now();
  auto b = end - start;

  std::cout << "Raw pointer time: "
    << duration_cast<microseconds>(a).count()
    << " microseconds\n";

  std::cout << "std::function time: "
    << duration_cast<microseconds>(b).count()
    << " microseconds\n";
}

The output might look something like this:

Raw pointer time: 353511 microseconds
std::function time: 428451 microseconds

Note that the actual numbers will vary depending on your system, compiler, and optimization settings.

2. Memory Usage

std::function uses more memory than a raw function pointer. It needs to allocate some extra space to handle its type-erasure mechanism and potential small object optimization.

3. Compile-Time vs. Runtime Polymorphism

Raw function pointers provide compile-time polymorphism, while std::function provides runtime polymorphism. This means that with raw function pointers, the compiler can potentially make more optimizations.

4. Default Arguments

Like raw function pointers, std::function doesn't preserve information about default arguments:

#include <functional>
#include <iostream>

void Greet(const char* name = "World") {
  std::cout << "Hello, " << name << "!\n";
}

int main() {
  std::function<void(const char*)> greet = Greet;

  // greet(); // This would be an error 
  greet("Alice");  // This works 
}
Hello, Alice!

5. Overload Resolution

std::function can sometimes make overload resolution more complex, especially when dealing with function templates. The following program generates a compiler error as it’s ambiguous what function we’re assigning to printFunc:

#include <iostream>
#include <functional>

template<typename T>
void Print(T value) {
  std::cout << "\nTemplate: " << value << "\n";
}

void Print(int value) {
  std::cout << "\nNon-Template: " << value << "\n";
}

int main() {
  std::function<void(int)> printFunc = Print;
}
error: 'initializing': cannot convert from 'overloaded-function' to 'std::function<void (int)>'

We can resolve the ambiguity like this:

#include <functional>
#include <iostream>

template <typename T>
void Print(T value) {
  std::cout << "\nTemplate: " << value;
}

void Print(int value) {
  std::cout << "\nNon-Template: " << value;
}

int main() {
  std::function printTemplateFunc = Print<int>;
  printTemplateFunc(5);

  std::function printFunc = static_cast<
    void(*)(int)>(Print);
  printFunc(5);
}
Template: 5
Non-Template: 5

6. Nullable State

Unlike raw function pointers, which can be compared directly to nullptr, std::function has a nullable state that's checked at runtime:

#include <functional>
#include <iostream>

void SomeFunction() {}

int main() {
  std::function<void()> func;

  if (func) {  
    std::cout << "func is not empty\n";
  } else {
    std::cout << "func is empty\n";
  }

  func = SomeFunction;

  if (func) {  
    std::cout << "Now func is not empty\n";
  }

  return 0;
}
func is empty
Now func is not empty

This runtime check can add a small overhead compared to raw function pointers.

Despite these limitations, std::function is often the preferred choice in modern C++ due to its flexibility and ease of use. The performance difference is negligible for most applications, and the benefits of type safety and flexibility often outweigh the drawbacks.

However, in performance-critical code or when working with older codebases, raw function pointers might still be the better choice.

This Question is from the Lesson:

Callbacks and Function Pointers

Learn to create flexible and modular code with function pointers

Answers to questions are automatically generated and may not have been reviewed.

This Question is from the Lesson:

Callbacks and Function Pointers

Learn to create flexible and modular code with function pointers

sdl2-promo.jpg
Part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 67 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Free, Unlimited Access

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved