c++pointersfunction-pointersshared-ptrsmart-pointers

segfaults in C++ when using std::shared_ptr


In the code bellow, when using std::shared_ptr I get a segmentation fault. However, when using normal pointers, the same problem doesn't occur.

In summary, I want to define a ConstrainedVariable object that will get updated according to some curve whenever the value of a reference variable is updated. The way I've found for implementing this involves using smart pointers, since they'll get automatically removed from the vectors holding them whenever they're no longer referenced (for example, when a variable is no longer used)

Based on information from gdb, the return of _refVal->val() is -nan. So the segfault is happening when calling _refCurve on this nan value.

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

using Action = std::function<void()>;
using ActionPtr = std::shared_ptr<Action>;
using ActionWeakPtr = std::weak_ptr<Action>;
using VectAct = std::vector<ActionWeakPtr>;
inline void execute(ActionPtr t){(*t)();}
inline ActionPtr action(Action t) {return std::make_shared<Action>(t);}

template<class T>
class Variable {
  protected:
    T ival; // internal value
    std::vector<ActionWeakPtr> actions;

  public:
    Variable() = default;

    inline void link(ActionPtr t) {actions.push_back(t);}

    void set(T newval) {
      if(ival == newval) return;
      static_set(newval);
      force_update();
    }

    T val() {return ival;}

  private:

    void static_set(T newval){
      ival = newval;
    }

    void force_update(){
      for(auto it = actions.begin(); it != actions.end(); /* Nothing */){
        auto action = it->lock();
        if(action){execute(action); it++;}
        else {it = actions.erase(it);}
      }
    }

};

template<class T>
using VariablePtr = std::shared_ptr<Variable<T>>;

template<class T>
inline VariablePtr<T> var() {return std::make_shared<Variable<T>>();};

template<class I, class O>
using Curve = std::function<O(I)>;

template<class I, class O>
class ConstrainedVariable : private Variable<O> {
  ActionPtr constraint;
  std::weak_ptr<Variable<I>> _refVar;
  Curve<I, O> _refCurve = [](I t){return std::sin(t);};

  public:
    void applyConstraint() {
      if(auto sharedVar = _refVar.lock()){
        this->set(_refCurve(sharedVar->val()));
      }
    }

    O val() {return this->ival;}

    ConstrainedVariable(VariablePtr<I> refVar, Curve<I, O> refCurve) :
      Variable<O>(), _refVar(refVar), _refCurve(refCurve) {
      constraint = action([this](){this->applyConstraint();});

      if(auto v = _refVar.lock()){
        v->link(constraint);
      }

      execute(constraint);
    }
};

template<class I, class O>
std::shared_ptr<ConstrainedVariable<I, O>> ctrVar(VariablePtr<I> refVar, Curve<I, O> refCurve){
  return std::make_shared<ConstrainedVariable<I, O>>(ConstrainedVariable<I, O>(refVar, refCurve));
};

int main(){
  auto v  = var<float>();

  //auto _c = ConstrainedVariable<float, float>(v, [](float t){return std::sin(t);});

  //auto* c = &_c;                                                        // WORKS
  auto c  = ctrVar<float, float>(v, [](float t){return std::sin(t);});  // DOESNT WORK

  std::cout << v->val() << std::endl;
  std::cout << c->val() << std::endl;

  v->set(1);

  std::cout << v->val() << std::endl;
  std::cout << c->val() << std::endl;

}

I haven't been able to minimally reproduce the issue nor do I understand why this is happening. Any help is appreciated.


Solution

  • The issue is in ctrVar. You construct a temporary ConstrainedVariable in the parameters to make_shared, which is then copied into the pointee.

    Because the action constraint is initialised with a lambda capturing this, it dangles when the original ceases to exist. ConstrainedVariable is not safe to copy or move, so you should = delete those special member functions, or fix them to register themselves when copied or moved.

    The immediate fix is to pass the constructor parameters to make_shared, so you don't copy ConstrainedVariables in your sample

    template<class I, class O>
    std::shared_ptr<ConstrainedVariable<I, O>> ctrVar(VariablePtr<I> refVar, Curve<I, O> refCurve){
      return std::make_shared<ConstrainedVariable<I, O>>(refVar, refCurve);
    };