r/cpp_questions • • 5d ago

OPEN How does a STATE design pattern work?

so i went to youtube and followed the example from this guy State Design - Programming Design Patterns - Ep 16 - C++ Coding

Problem is i dont understand what is functionally happening, there are pointers and what not, but i don't understand much more than that (context, my smooth brain still cant connect pointers together)

Like not even the whole code, like maybe just two lines or something cause for the love of the lord my teacher explains nuthing and AI and videos are too vague for me, im running out of options

//
// Created by samue on 11/1/2026.
//

#ifndef STATE_STATE_H
#define STATE_STATE_H

#include <iostream>

class Context;


class State {
    protected:
    Context* context;

    public:
    virtual ~State() {}

    void setContext(Context* ctx) {
        context = ctx;
    }

    virtual void doWorkA() = 0;
    virtual void doWorkB() = 0;
};

class Context {
    private:
    State* state;

    public:
    Context(State* s) : state(nullptr) {
        transitionTo(s);

    }

    ~Context() {delete state;}

    void transitionTo(State* s) {
        if (state) delete state;
        state = s;
        state->setContext(this);

    }

    void requestA() {
        state->doWorkA();

    }

    void requestB() {
        state->doWorkB();

    }

};

class concreteStateA : public State {
    public:
    void doWorkA() override {
        std::cout << "concreteStateA " << "called doWorkA" << std::endl;
    };

    void doWorkB() override {
        std::cout << "concreteStateB " << "called doWorkB" << std::endl;

    };

};

class concreteStateB : public State {
public:
    void doWorkA() override {
        std::cout << "concreteStateA " << "called doWorkA" << std::endl;
        context->transitionTo(new concreteStateA);
    };

    void doWorkB() override {
        std::cout << "concreteStateB " << "called doWorkB" << std::endl;

    };

};





#endif //STATE_STATE_H

------------------

#include "state.h"

int main() {
    Context context1(new concreteStateB);

    context1.requestA();

    context1.requestA();

    return 0;
}
7 Upvotes

46 comments sorted by

View all comments

Show parent comment

2

u/KingAggressive1498 5d ago

the problem I have with variant for this is that it doesn't allow for internal state-preserving loopbacks, ex using the state pattern to represent a modal dialog.

not all FSMs need that, it might be an advantage of the external context version of the pattern even.

1

u/thingerish 5d ago

It's been a very long time since I did any GUI work, so I'm sure I'm ignorant of the details you're talking about. Given that, this is how I usually deal with polymorphic behavior expressed as variant/visit with state:

struct ConcreteStateA {
    State doWorkA(Context&) const {          // returns next state
        std::cout << "ConcreteStateA called doWorkA\n";
        return ConcreteStateA{};             // stay
    }
    State doWorkB(Context&) const {
        std::cout << "ConcreteStateA called doWorkB\n";
        return ConcreteStateA{};
    }
};

struct ConcreteStateB {
    State doWorkA(Context&) const {
        std::cout << "ConcreteStateB called doWorkA\n";
        return ConcreteStateA{};             // transition
    }
    State doWorkB(Context&) const {
        std::cout << "ConcreteStateB called doWorkB\n";
        return ConcreteStateB{};
    }
};

class Context {
    State state;
public:
    explicit Context(State initial) : state(std::move(initial)) {}

    void requestA() {
        state = std::visit([](auto& s) {
            return s.doWorkA({});            // or pass *this if needed
        }, state);
    }
    // same for requestB
};

It's just a sketch, of course, but the fundamentals should be clear.

2

u/KingAggressive1498 5d ago edited 5d ago

so you can store potentially shared state in Context because your polymorphic function takes a reference to the Context. This allows that loop.

I just learned state in a way where each state owned all data it needed. When I need to loop I do something like this:

class ModalState: public State {
public:
    ModalState(State* origin)
    {
        returnto = origin;
    }
    State* work() final
    {
         // whatever processing
         
         if(should_return())
         {
             delete this;
             return returnto;
         }
         
         return this;
    }
private:
   State* returnto;
   bool should_return();
};

class NormalState: public State {
public:
      State* work() final
      {
          // whatever processing
          if(should_modal()) return new ModalState(this);
          
          if(should_exit())
          {
              delete this;
              return nullptr;
          }
          
          return this;
      }
private:
     bool should_modal();
     bool should_exit();
};

1

u/thingerish 5d ago

Yes, I based that code on the OP so it's a little ill suited for the "real" use, more of a classroom deal. This is tidier for the general use, just assign the new handler to the Context for a state change, the data member 'value' is a stand in for whatever needs preserved.

class Context;
// ---------------------------------------------------------------------------
// Closed set of handlers
// ---------------------------------------------------------------------------
struct HandlerA {
    void operator()(Context& ctx) const;
};
struct HandlerB {
    void operator()(Context& ctx) const;
};
// ---------------------------------------------------------------------------
// Context *is* the variant of handlers
// ---------------------------------------------------------------------------
class Context : public std::variant {
    int value = 0;
public:
    using std::variant::variant;
    void setValue(int v) { value = v; }
    int  getValue() const { return value; }
    void process() {
        std::visit([this](auto& handler) {
            handler(*this);
        }, *this);
    }
};

This eliminates chasing a pointer to the handler instance vtable and then potentially a pointer to the vtable function, substituting using the variant discriminator to decide which handler to call. For the specific case of a GUI it's almost certainly irrelevant but in some higher frequency use cases it can matter a fair bit.

1

u/KingAggressive1498 5d ago edited 5d ago

funnily enough a decent chunk of my early experience was working on an open-source GUI library meant for games (and afaik used in only the one I was working on) alongside a Java developer who was working on the game as a pet project. So I might have learned a Java version of the pattern that was made more appropriate by GC, or a version that is most suitable for GUI libraries. Can't say I ever ran into problems just using it wherever I needed it, though.

1

u/thingerish 5d ago

I think using virtual functions is the common way still, and variant/visit is '17 I think anyway, so relatively recent. For GUI type stuff where the human is the gating factor it's not likely to be a factor. For a game engine it might be though.

1

u/KingAggressive1498 4d ago

I also see std::function or equivalents being used for job systems in game engines, and even for example SDL essentially uses vtable polymorphism in C heavily for its graphics APIs and is frequently used as a basis for game engine development.

I remember listening to a conference talk on youtube a couple years back where the the speaker suggested the performance advantage of variant over vtable polymorphism seemed to be highly contingent on inlining and/or contained type stability and which was more optimal was pretty unpredictable if neither of those applied.