c++templatesbindrefdynamic-cast

pass shared_ptr to std::bind through std::ref will strip it's polymorphism


My program crashed by SIGSEGV when I try to dynamic_cast a Base class "this" pointer to a Derived class pointer. I am wondering if it has anything to do with std::bind and std::ref. Below is my example code which can produce the problem.

// header h.hpp

#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <vector>

using namespace std;

template <typename T> 
class Derived;

class Base {
public:
  virtual ~Base() {}

  template <typename T> 
  void foo(const T &t) {
    cout << "Base foo\n";
    auto derived = dynamic_cast<Derived<T> *>(this); // SIGSEGV crash!
    derived->foo(t);
  }

private:
};

template <typename T> 
class Derived : public Base {
public:
  void foo(const T &t) { cout << "Derived foo\n"; }

private:
};

class Outter {
public:
  void init();
  void run();

private:
  void run_foo(shared_ptr<Base> &base);

  class Inner {
  public:
    void run() {
      for (const auto &f : foos_) {
        f();
      }
    }

    void set_foo(function<void()> f) { 
        foos_.push_back(f); 
    }

  private:
    static vector<function<void()>> foos_;
  };

  Inner inner_;
  vector<shared_ptr<Base>> bases_;
};
// source main.cpp

#include "h.hpp"

vector<function<void()>> Outter::Inner::foos_;

void Outter::init() {
  shared_ptr<Base> base = make_shared<Derived<int>>();
  bases_.push_back(base);
  
//   inner_.set_foo(bind(&Outter::run_foo, this, base)); // no ref version
  inner_.set_foo(bind(&Outter::run_foo, this, ref(base))); // ref verison
}

void Outter::run() { inner_.run(); }

void Outter::run_foo(shared_ptr<Base> &base) {
  int t = 123;
  base->foo(t);
}

int main() {
  Outter outter;
  outter.init();
  outter.run();
  return 0;
}

If I commented out the ref version and use the "no-ref" version, the program runs fine and won't break. It seems like the std::ref function is chopping the polymorphism off of the Base shared_ptr, why is that so?


Solution

  • Nothing to do with shared_ptr or polymorphism, your shared_ptr<Base> base simply goes out of scope.

    Consider this example:

    void foo(std::shared_ptr<int>& ptr)
    {
        std::cout << *ptr << std::endl;
    }
    
    int main()
    {
        std::function<void()> f;
        std::shared_ptr<int> backup;
        {
            std::shared_ptr<int> ptr{new int{7}};
            backup = ptr;
            f = std::bind(foo, std::ref(ptr));
            // f = std::bind(foo, ptr);
        }
        f();
        return 0;
    }
    

    If you use std::ref, calling f(); will have undefined behaviour, because ptr is out of scope and foo has a dangling reference, regardless of the fact that the integer it managed is still shared with another shared_ptr still in the scope.

    example with addr sanitizer