diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 215ef7933c..4adedf6e94 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1122,7 +1122,6 @@ COMMON_SFILES = \
 	test-target.c \
 	thread.c \
 	thread-iter.c \
-	thread-fsm.c \
 	tid-parse.c \
 	top.c \
 	tracefile.c \
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index d54d496972..ffbec7001a 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -10967,106 +10967,66 @@ awatch_command (const char *arg, int from_tty)
    in infcmd.c.  Here because it uses the mechanisms of
    breakpoints.  */
 
-struct until_break_fsm
+struct until_break_fsm : public thread_fsm
 {
-  /* The base class.  */
-  struct thread_fsm thread_fsm;
-
   /* The thread that as current when the command was executed.  */
   int thread;
 
   /* The breakpoint set at the destination location.  */
-  struct breakpoint *location_breakpoint;
+  breakpoint_up location_breakpoint;
 
   /* Breakpoint set at the return address in the caller frame.  May be
      NULL.  */
-  struct breakpoint *caller_breakpoint;
-};
-
-static void until_break_fsm_clean_up (struct thread_fsm *self,
-				      struct thread_info *thread);
-static int until_break_fsm_should_stop (struct thread_fsm *self,
-					struct thread_info *thread);
-static enum async_reply_reason
-  until_break_fsm_async_reply_reason (struct thread_fsm *self);
+  breakpoint_up caller_breakpoint;
 
-/* until_break_fsm's vtable.  */
+  until_break_fsm (struct interp *cmd_interp, int thread,
+		   breakpoint_up &&location_breakpoint,
+		   breakpoint_up &&caller_breakpoint)
+    : thread_fsm (cmd_interp),
+      thread (thread),
+      location_breakpoint (std::move (location_breakpoint)),
+      caller_breakpoint (std::move (caller_breakpoint))
+  {
+  }
 
-static struct thread_fsm_ops until_break_fsm_ops =
-{
-  NULL, /* dtor */
-  until_break_fsm_clean_up,
-  until_break_fsm_should_stop,
-  NULL, /* return_value */
-  until_break_fsm_async_reply_reason,
+  void clean_up (struct thread_info *thread) override;
+  bool should_stop (struct thread_info *thread) override;
+  enum async_reply_reason do_async_reply_reason () override;
 };
 
-/* Allocate a new until_break_command_fsm.  */
-
-static struct until_break_fsm *
-new_until_break_fsm (struct interp *cmd_interp, int thread,
-		     breakpoint_up &&location_breakpoint,
-		     breakpoint_up &&caller_breakpoint)
-{
-  struct until_break_fsm *sm;
-
-  sm = XCNEW (struct until_break_fsm);
-  thread_fsm_ctor (&sm->thread_fsm, &until_break_fsm_ops, cmd_interp);
-
-  sm->thread = thread;
-  sm->location_breakpoint = location_breakpoint.release ();
-  sm->caller_breakpoint = caller_breakpoint.release ();
-
-  return sm;
-}
-
 /* Implementation of the 'should_stop' FSM method for the
    until(location)/advance commands.  */
 
-static int
-until_break_fsm_should_stop (struct thread_fsm *self,
-			     struct thread_info *tp)
+bool
+until_break_fsm::should_stop (struct thread_info *tp)
 {
-  struct until_break_fsm *sm = (struct until_break_fsm *) self;
-
   if (bpstat_find_breakpoint (tp->control.stop_bpstat,
-			      sm->location_breakpoint) != NULL
-      || (sm->caller_breakpoint != NULL
+			      location_breakpoint.get ()) != NULL
+      || (caller_breakpoint != NULL
 	  && bpstat_find_breakpoint (tp->control.stop_bpstat,
-				     sm->caller_breakpoint) != NULL))
-    thread_fsm_set_finished (self);
+				     caller_breakpoint.get ()) != NULL))
+    set_finished ();
 
-  return 1;
+  return true;
 }
 
 /* Implementation of the 'clean_up' FSM method for the
    until(location)/advance commands.  */
 
-static void
-until_break_fsm_clean_up (struct thread_fsm *self,
-			  struct thread_info *thread)
+void
+until_break_fsm::clean_up (struct thread_info *)
 {
-  struct until_break_fsm *sm = (struct until_break_fsm *) self;
-
   /* Clean up our temporary breakpoints.  */
-  if (sm->location_breakpoint != NULL)
-    {
-      delete_breakpoint (sm->location_breakpoint);
-      sm->location_breakpoint = NULL;
-    }
-  if (sm->caller_breakpoint != NULL)
-    {
-      delete_breakpoint (sm->caller_breakpoint);
-      sm->caller_breakpoint = NULL;
-    }
-  delete_longjmp_breakpoint (sm->thread);
+  location_breakpoint.reset ();
+  caller_breakpoint.reset ();
+  delete_longjmp_breakpoint (thread);
 }
 
 /* Implementation of the 'async_reply_reason' FSM method for the
    until(location)/advance commands.  */
 
-static enum async_reply_reason
-until_break_fsm_async_reply_reason (struct thread_fsm *self)
+enum async_reply_reason
+until_break_fsm::do_async_reply_reason ()
 {
   return EXEC_ASYNC_LOCATION_REACHED;
 }
@@ -11081,7 +11041,6 @@ until_break_command (const char *arg, int from_tty, int anywhere)
   struct cleanup *old_chain;
   int thread;
   struct thread_info *tp;
-  struct until_break_fsm *sm;
 
   clear_proceed_status (0);
 
@@ -11159,10 +11118,9 @@ until_break_command (const char *arg, int from_tty, int anywhere)
     location_breakpoint = set_momentary_breakpoint (frame_gdbarch, sal,
 						    stack_frame_id, bp_until);
 
-  sm = new_until_break_fsm (command_interp (), tp->global_num,
-			    std::move (location_breakpoint),
-			    std::move (caller_breakpoint));
-  tp->thread_fsm = &sm->thread_fsm;
+  tp->thread_fsm = new until_break_fsm (command_interp (), tp->global_num,
+					std::move (location_breakpoint),
+					std::move (caller_breakpoint));
 
   discard_cleanups (old_chain);
 
diff --git a/gdb/cli/cli-interp.c b/gdb/cli/cli-interp.c
index 8a3f65f088..58cf306c71 100644
--- a/gdb/cli/cli-interp.c
+++ b/gdb/cli/cli-interp.c
@@ -114,7 +114,7 @@ should_print_stop_to_console (struct interp *console_interp,
        == BPSTAT_WHAT_STOP_NOISY)
       || tp->thread_fsm == NULL
       || tp->thread_fsm->command_interp == console_interp
-      || !thread_fsm_finished_p (tp->thread_fsm))
+      || !tp->thread_fsm->finished_p ())
     return 1;
   return 0;
 }
diff --git a/gdb/infcall.c b/gdb/infcall.c
index 14b0cbc716..0b2ffa0254 100644
--- a/gdb/infcall.c
+++ b/gdb/infcall.c
@@ -469,106 +469,87 @@ get_call_return_value (struct call_return_meta_info *ri)
 /* Data for the FSM that manages an infcall.  It's main job is to
    record the called function's return value.  */
 
-struct call_thread_fsm
+struct call_thread_fsm : public thread_fsm
 {
-  /* The base class.  */
-  struct thread_fsm thread_fsm;
-
   /* All the info necessary to be able to extract the return
      value.  */
   struct call_return_meta_info return_meta_info;
 
   /* The called function's return value.  This is extracted from the
      target before the dummy frame is popped.  */
-  struct value *return_value;
+  struct value *return_value = nullptr;
 
   /* The top level that started the infcall (and is synchronously
      waiting for it to end).  */
   struct ui *waiting_ui;
-};
 
-static int call_thread_fsm_should_stop (struct thread_fsm *self,
-					struct thread_info *thread);
-static int call_thread_fsm_should_notify_stop (struct thread_fsm *self);
+  call_thread_fsm (struct ui *waiting_ui, struct interp *cmd_interp,
+		   struct gdbarch *gdbarch, struct value *function,
+		   struct type *value_type,
+		   int struct_return_p, CORE_ADDR struct_addr);
 
-/* call_thread_fsm's vtable.  */
+  bool should_stop (struct thread_info *thread) override;
 
-static struct thread_fsm_ops call_thread_fsm_ops =
-{
-  NULL, /*dtor */
-  NULL, /* clean_up */
-  call_thread_fsm_should_stop,
-  NULL, /* return_value */
-  NULL, /* async_reply_reason*/
-  call_thread_fsm_should_notify_stop,
+  bool should_notify_stop () override;
 };
 
 /* Allocate a new call_thread_fsm object.  */
 
-static struct call_thread_fsm *
-new_call_thread_fsm (struct ui *waiting_ui, struct interp *cmd_interp,
-		     struct gdbarch *gdbarch, struct value *function,
-		     struct type *value_type,
-		     int struct_return_p, CORE_ADDR struct_addr)
+call_thread_fsm::call_thread_fsm (struct ui *waiting_ui,
+				  struct interp *cmd_interp,
+				  struct gdbarch *gdbarch,
+				  struct value *function,
+				  struct type *value_type,
+				  int struct_return_p, CORE_ADDR struct_addr)
+  : thread_fsm (cmd_interp),
+    waiting_ui (waiting_ui)
 {
-  struct call_thread_fsm *sm;
-
-  sm = XCNEW (struct call_thread_fsm);
-  thread_fsm_ctor (&sm->thread_fsm, &call_thread_fsm_ops, cmd_interp);
-
-  sm->return_meta_info.gdbarch = gdbarch;
-  sm->return_meta_info.function = function;
-  sm->return_meta_info.value_type = value_type;
-  sm->return_meta_info.struct_return_p = struct_return_p;
-  sm->return_meta_info.struct_addr = struct_addr;
-
-  sm->waiting_ui = waiting_ui;
-
-  return sm;
+  return_meta_info.gdbarch = gdbarch;
+  return_meta_info.function = function;
+  return_meta_info.value_type = value_type;
+  return_meta_info.struct_return_p = struct_return_p;
+  return_meta_info.struct_addr = struct_addr;
 }
 
 /* Implementation of should_stop method for infcalls.  */
 
-static int
-call_thread_fsm_should_stop (struct thread_fsm *self,
-			     struct thread_info *thread)
+bool
+call_thread_fsm::should_stop (struct thread_info *thread)
 {
-  struct call_thread_fsm *f = (struct call_thread_fsm *) self;
-
   if (stop_stack_dummy == STOP_STACK_DUMMY)
     {
       /* Done.  */
-      thread_fsm_set_finished (self);
+      set_finished ();
 
       /* Stash the return value before the dummy frame is popped and
 	 registers are restored to what they were before the
 	 call..  */
-      f->return_value = get_call_return_value (&f->return_meta_info);
+      return_value = get_call_return_value (&return_meta_info);
 
       /* Break out of wait_sync_command_done.  */
-      scoped_restore save_ui = make_scoped_restore (&current_ui, f->waiting_ui);
+      scoped_restore save_ui = make_scoped_restore (&current_ui, waiting_ui);
       target_terminal::ours ();
-      f->waiting_ui->prompt_state = PROMPT_NEEDED;
+      waiting_ui->prompt_state = PROMPT_NEEDED;
     }
 
-  return 1;
+  return true;
 }
 
 /* Implementation of should_notify_stop method for infcalls.  */
 
-static int
-call_thread_fsm_should_notify_stop (struct thread_fsm *self)
+bool
+call_thread_fsm::should_notify_stop ()
 {
-  if (thread_fsm_finished_p (self))
+  if (finished_p ())
     {
       /* Infcall succeeded.  Be silent and proceed with evaluating the
 	 expression.  */
-      return 0;
+      return false;
     }
 
   /* Something wrong happened.  E.g., an unexpected breakpoint
      triggered, or a signal was intercepted.  Notify the stop.  */
-  return 1;
+  return true;
 }
 
 /* Subroutine of call_function_by_hand to simplify it.
@@ -605,7 +586,7 @@ run_inferior_call (struct call_thread_fsm *sm,
   /* Associate the FSM with the thread after clear_proceed_status
      (otherwise it'd clear this FSM), and before anything throws, so
      we don't leak it (and any resources it manages).  */
-  call_thread->thread_fsm = &sm->thread_fsm;
+  call_thread->thread_fsm = sm;
 
   disable_watchpoints_before_interactive_call_start ();
 
@@ -1147,7 +1128,7 @@ call_function_by_hand_dummy (struct value *function,
        not report the stop to the user, and captures the return value
        before the dummy frame is popped.  run_inferior_call registers
        it with the thread ASAP.  */
-    sm = new_call_thread_fsm (current_ui, command_interp (),
+    sm = new call_thread_fsm (current_ui, command_interp (),
 			      gdbarch, function,
 			      values_type,
 			      return_method != return_method_normal,
@@ -1160,9 +1141,9 @@ call_function_by_hand_dummy (struct value *function,
     if (call_thread->state != THREAD_EXITED)
       {
 	/* The FSM should still be the same.  */
-	gdb_assert (call_thread->thread_fsm == &sm->thread_fsm);
+	gdb_assert (call_thread->thread_fsm == sm);
 
-	if (thread_fsm_finished_p (call_thread->thread_fsm))
+	if (call_thread->thread_fsm->finished_p ())
 	  {
 	    struct value *retval;
 
@@ -1178,8 +1159,8 @@ call_function_by_hand_dummy (struct value *function,
 
 	    /* Clean up / destroy the call FSM, and restore the
 	       original one.  */
-	    thread_fsm_clean_up (call_thread->thread_fsm, call_thread.get ());
-	    thread_fsm_delete (call_thread->thread_fsm);
+	    call_thread->thread_fsm->clean_up (call_thread.get ());
+	    delete call_thread->thread_fsm;
 	    call_thread->thread_fsm = saved_sm;
 
 	    maybe_remove_breakpoints ();
@@ -1191,8 +1172,8 @@ call_function_by_hand_dummy (struct value *function,
 
 	/* Didn't complete.  Clean up / destroy the call FSM, and restore the
 	   previous state machine, and handle the error.  */
-	thread_fsm_clean_up (call_thread->thread_fsm, call_thread.get ());
-	thread_fsm_delete (call_thread->thread_fsm);
+	call_thread->thread_fsm->clean_up (call_thread.get ());
+	delete call_thread->thread_fsm;
 	call_thread->thread_fsm = saved_sm;
       }
   }
diff --git a/gdb/infcmd.c b/gdb/infcmd.c
index 182960ab99..d3d97445ad 100644
--- a/gdb/infcmd.c
+++ b/gdb/infcmd.c
@@ -957,11 +957,8 @@ delete_longjmp_breakpoint_cleanup (void *arg)
 /* Data for the FSM that manages the step/next/stepi/nexti
    commands.  */
 
-struct step_command_fsm
+struct step_command_fsm : public thread_fsm
 {
-  /* The base class.  */
-  struct thread_fsm thread_fsm;
-
   /* How many steps left in a "step N"-like command.  */
   int count;
 
@@ -970,39 +967,17 @@ struct step_command_fsm
 
   /* If true, this is a stepi/nexti, otherwise a step/step.  */
   int single_inst;
-};
 
-static void step_command_fsm_clean_up (struct thread_fsm *self,
-				       struct thread_info *thread);
-static int step_command_fsm_should_stop (struct thread_fsm *self,
-					 struct thread_info *thread);
-static enum async_reply_reason
-  step_command_fsm_async_reply_reason (struct thread_fsm *self);
-
-/* step_command_fsm's vtable.  */
+  explicit step_command_fsm (struct interp *cmd_interp)
+    : thread_fsm (cmd_interp)
+  {
+  }
 
-static struct thread_fsm_ops step_command_fsm_ops =
-{
-  NULL,
-  step_command_fsm_clean_up,
-  step_command_fsm_should_stop,
-  NULL,	/* return_value */
-  step_command_fsm_async_reply_reason,
+  void clean_up (struct thread_info *thread) override;
+  bool should_stop (struct thread_info *thread) override;
+  enum async_reply_reason do_async_reply_reason () override;
 };
 
-/* Allocate a new step_command_fsm.  */
-
-static struct step_command_fsm *
-new_step_command_fsm (struct interp *cmd_interp)
-{
-  struct step_command_fsm *sm;
-
-  sm = XCNEW (struct step_command_fsm);
-  thread_fsm_ctor (&sm->thread_fsm, &step_command_fsm_ops, cmd_interp);
-
-  return sm;
-}
-
 /* Prepare for a step/next/etc. command.  Any target resource
    allocated here is undone in the FSM's clean_up method.  */
 
@@ -1050,8 +1025,8 @@ step_1 (int skip_subroutines, int single_inst, const char *count_string)
   /* Setup the execution command state machine to handle all the COUNT
      steps.  */
   thr = inferior_thread ();
-  step_sm = new_step_command_fsm (command_interp ());
-  thr->thread_fsm = &step_sm->thread_fsm;
+  step_sm = new step_command_fsm (command_interp ());
+  thr->thread_fsm = step_sm;
 
   step_command_fsm_prepare (step_sm, skip_subroutines,
 			    single_inst, count, thr);
@@ -1068,7 +1043,7 @@ step_1 (int skip_subroutines, int single_inst, const char *count_string)
 
       /* Stepped into an inline frame.  Pretend that we've
 	 stopped.  */
-      thread_fsm_clean_up (thr->thread_fsm, thr);
+      thr->thread_fsm->clean_up (thr);
       proceeded = normal_stop ();
       if (!proceeded)
 	inferior_event_handler (INF_EXEC_COMPLETE, NULL);
@@ -1082,40 +1057,36 @@ step_1 (int skip_subroutines, int single_inst, const char *count_string)
    return control to the user.  If count is > 1, returns false, as we
    will need to keep going.  */
 
-static int
-step_command_fsm_should_stop (struct thread_fsm *self, struct thread_info *tp)
+bool
+step_command_fsm::should_stop (struct thread_info *tp)
 {
-  struct step_command_fsm *sm = (struct step_command_fsm *) self;
-
   if (tp->control.stop_step)
     {
       /* There are more steps to make, and we did stop due to
 	 ending a stepping range.  Do another step.  */
-      if (--sm->count > 0)
-	return prepare_one_step (sm);
+      if (--count > 0)
+	return prepare_one_step (this);
 
-      thread_fsm_set_finished (self);
+      set_finished ();
     }
 
-  return 1;
+  return true;
 }
 
 /* Implementation of the 'clean_up' FSM method for stepping commands.  */
 
-static void
-step_command_fsm_clean_up (struct thread_fsm *self, struct thread_info *thread)
+void
+step_command_fsm::clean_up (struct thread_info *thread)
 {
-  struct step_command_fsm *sm = (struct step_command_fsm *) self;
-
-  if (!sm->single_inst || sm->skip_subroutines)
+  if (!single_inst || skip_subroutines)
     delete_longjmp_breakpoint (thread->global_num);
 }
 
 /* Implementation of the 'async_reply_reason' FSM method for stepping
    commands.  */
 
-static enum async_reply_reason
-step_command_fsm_async_reply_reason (struct thread_fsm *self)
+enum async_reply_reason
+step_command_fsm::do_async_reply_reason ()
 {
   return EXEC_ASYNC_END_STEPPING_RANGE;
 }
@@ -1205,7 +1176,7 @@ prepare_one_step (struct step_command_fsm *sm)
     }
 
   /* Done.  */
-  thread_fsm_set_finished (&sm->thread_fsm);
+  sm->set_finished ();
   return 1;
 }
 
@@ -1427,66 +1398,39 @@ queue_signal_command (const char *signum_exp, int from_tty)
 /* Data for the FSM that manages the until (with no argument)
    command.  */
 
-struct until_next_fsm
+struct until_next_fsm : public thread_fsm
 {
-  /* The base class.  */
-  struct thread_fsm thread_fsm;
-
   /* The thread that as current when the command was executed.  */
   int thread;
-};
-
-static int until_next_fsm_should_stop (struct thread_fsm *self,
-				       struct thread_info *thread);
-static void until_next_fsm_clean_up (struct thread_fsm *self,
-				     struct thread_info *thread);
-static enum async_reply_reason
-  until_next_fsm_async_reply_reason (struct thread_fsm *self);
 
-/* until_next_fsm's vtable.  */
+  until_next_fsm (struct interp *cmd_interp, int thread)
+    : thread_fsm (cmd_interp),
+      thread (thread)
+  {
+  }
 
-static struct thread_fsm_ops until_next_fsm_ops =
-{
-  NULL, /* dtor */
-  until_next_fsm_clean_up,
-  until_next_fsm_should_stop,
-  NULL, /* return_value */
-  until_next_fsm_async_reply_reason,
+  bool should_stop (struct thread_info *thread) override;
+  void clean_up (struct thread_info *thread) override;
+  enum async_reply_reason do_async_reply_reason () override;
 };
 
-/* Allocate a new until_next_fsm.  */
-
-static struct until_next_fsm *
-new_until_next_fsm (struct interp *cmd_interp, int thread)
-{
-  struct until_next_fsm *sm;
-
-  sm = XCNEW (struct until_next_fsm);
-  thread_fsm_ctor (&sm->thread_fsm, &until_next_fsm_ops, cmd_interp);
-
-  sm->thread = thread;
-
-  return sm;
-}
-
 /* Implementation of the 'should_stop' FSM method for the until (with
    no arg) command.  */
 
-static int
-until_next_fsm_should_stop (struct thread_fsm *self,
-			    struct thread_info *tp)
+bool
+until_next_fsm::should_stop (struct thread_info *tp)
 {
   if (tp->control.stop_step)
-    thread_fsm_set_finished (self);
+    set_finished ();
 
-  return 1;
+  return true;
 }
 
 /* Implementation of the 'clean_up' FSM method for the until (with no
    arg) command.  */
 
-static void
-until_next_fsm_clean_up (struct thread_fsm *self, struct thread_info *thread)
+void
+until_next_fsm::clean_up (struct thread_info *thread)
 {
   delete_longjmp_breakpoint (thread->global_num);
 }
@@ -1494,8 +1438,8 @@ until_next_fsm_clean_up (struct thread_fsm *self, struct thread_info *thread)
 /* Implementation of the 'async_reply_reason' FSM method for the until
    (with no arg) command.  */
 
-static enum async_reply_reason
-until_next_fsm_async_reply_reason (struct thread_fsm *self)
+enum async_reply_reason
+until_next_fsm::do_async_reply_reason ()
 {
   return EXEC_ASYNC_END_STEPPING_RANGE;
 }
@@ -1558,8 +1502,8 @@ until_next_command (int from_tty)
   set_longjmp_breakpoint (tp, get_frame_id (frame));
   old_chain = make_cleanup (delete_longjmp_breakpoint_cleanup, &thread);
 
-  sm = new_until_next_fsm (command_interp (), tp->global_num);
-  tp->thread_fsm = &sm->thread_fsm;
+  sm = new until_next_fsm (command_interp (), tp->global_num);
+  tp->thread_fsm = sm;
   discard_cleanups (old_chain);
 
   proceed ((CORE_ADDR) -1, GDB_SIGNAL_DEFAULT);
@@ -1725,77 +1669,48 @@ print_return_value (struct ui_out *uiout, struct return_value_info *rv)
 
 /* Data for the FSM that manages the finish command.  */
 
-struct finish_command_fsm
+struct finish_command_fsm : public thread_fsm
 {
-  /* The base class.  */
-  struct thread_fsm thread_fsm;
-
   /* The momentary breakpoint set at the function's return address in
      the caller.  */
-  struct breakpoint *breakpoint;
+  breakpoint_up breakpoint;
 
   /* The function that we're stepping out of.  */
-  struct symbol *function;
+  struct symbol *function = nullptr;
 
   /* If the FSM finishes successfully, this stores the function's
      return value.  */
-  struct return_value_info return_value;
-};
+  struct return_value_info return_value_info {};
 
-static int finish_command_fsm_should_stop (struct thread_fsm *self,
-					   struct thread_info *thread);
-static void finish_command_fsm_clean_up (struct thread_fsm *self,
-					 struct thread_info *thread);
-static struct return_value_info *
-  finish_command_fsm_return_value (struct thread_fsm *self);
-static enum async_reply_reason
-  finish_command_fsm_async_reply_reason (struct thread_fsm *self);
-
-/* finish_command_fsm's vtable.  */
-
-static struct thread_fsm_ops finish_command_fsm_ops =
-{
-  NULL, /* dtor */
-  finish_command_fsm_clean_up,
-  finish_command_fsm_should_stop,
-  finish_command_fsm_return_value,
-  finish_command_fsm_async_reply_reason,
-  NULL, /* should_notify_stop */
-};
-
-/* Allocate a new finish_command_fsm.  */
-
-static struct finish_command_fsm *
-new_finish_command_fsm (struct interp *cmd_interp)
-{
-  struct finish_command_fsm *sm;
-
-  sm = XCNEW (struct finish_command_fsm);
-  thread_fsm_ctor (&sm->thread_fsm, &finish_command_fsm_ops, cmd_interp);
+  explicit finish_command_fsm (struct interp *cmd_interp)
+    : thread_fsm (cmd_interp)
+  {
+  }
 
-  return sm;
-}
+  bool should_stop (struct thread_info *thread) override;
+  void clean_up (struct thread_info *thread) override;
+  struct return_value_info *return_value () override;
+  enum async_reply_reason do_async_reply_reason () override;
+};
 
 /* Implementation of the 'should_stop' FSM method for the finish
    commands.  Detects whether the thread stepped out of the function
    successfully, and if so, captures the function's return value and
    marks the FSM finished.  */
 
-static int
-finish_command_fsm_should_stop (struct thread_fsm *self,
-				struct thread_info *tp)
+bool
+finish_command_fsm::should_stop (struct thread_info *tp)
 {
-  struct finish_command_fsm *f = (struct finish_command_fsm *) self;
-  struct return_value_info *rv = &f->return_value;
+  struct return_value_info *rv = &return_value_info;
 
-  if (f->function != NULL
+  if (function != NULL
       && bpstat_find_breakpoint (tp->control.stop_bpstat,
-				 f->breakpoint) != NULL)
+				 breakpoint.get ()) != NULL)
     {
       /* We're done.  */
-      thread_fsm_set_finished (self);
+      set_finished ();
 
-      rv->type = TYPE_TARGET_TYPE (SYMBOL_TYPE (f->function));
+      rv->type = TYPE_TARGET_TYPE (SYMBOL_TYPE (function));
       if (rv->type == NULL)
 	internal_error (__FILE__, __LINE__,
 			_("finish_command: function has no target type"));
@@ -1804,7 +1719,7 @@ finish_command_fsm_should_stop (struct thread_fsm *self,
 	{
 	  struct value *func;
 
-	  func = read_var_value (f->function, NULL, get_current_frame ());
+	  func = read_var_value (function, NULL, get_current_frame ());
 	  rv->value = get_return_value (func, rv->type);
 	  if (rv->value != NULL)
 	    rv->value_history_index = record_latest_value (rv->value);
@@ -1814,45 +1729,36 @@ finish_command_fsm_should_stop (struct thread_fsm *self,
     {
       /* Finishing from an inline frame, or reverse finishing.  In
 	 either case, there's no way to retrieve the return value.  */
-      thread_fsm_set_finished (self);
+      set_finished ();
     }
 
-  return 1;
+  return true;
 }
 
 /* Implementation of the 'clean_up' FSM method for the finish
    commands.  */
 
-static void
-finish_command_fsm_clean_up (struct thread_fsm *self,
-			     struct thread_info *thread)
+void
+finish_command_fsm::clean_up (struct thread_info *thread)
 {
-  struct finish_command_fsm *f = (struct finish_command_fsm *) self;
-
-  if (f->breakpoint != NULL)
-    {
-      delete_breakpoint (f->breakpoint);
-      f->breakpoint = NULL;
-    }
+  breakpoint.reset ();
   delete_longjmp_breakpoint (thread->global_num);
 }
 
 /* Implementation of the 'return_value' FSM method for the finish
    commands.  */
 
-static struct return_value_info *
-finish_command_fsm_return_value (struct thread_fsm *self)
+struct return_value_info *
+finish_command_fsm::return_value ()
 {
-  struct finish_command_fsm *f = (struct finish_command_fsm *) self;
-
-  return &f->return_value;
+  return &return_value_info;
 }
 
 /* Implementation of the 'async_reply_reason' FSM method for the
    finish commands.  */
 
-static enum async_reply_reason
-finish_command_fsm_async_reply_reason (struct thread_fsm *self)
+enum async_reply_reason
+finish_command_fsm::do_async_reply_reason ()
 {
   if (execution_direction == EXEC_REVERSE)
     return EXEC_ASYNC_END_STEPPING_RANGE;
@@ -1927,7 +1833,7 @@ finish_forward (struct finish_command_fsm *sm, struct frame_info *frame)
 
   sm->breakpoint = set_momentary_breakpoint (gdbarch, sal,
 					     get_stack_frame_id (frame),
-					     bp_finish).release ();
+					     bp_finish);
 
   /* set_momentary_breakpoint invalidates FRAME.  */
   frame = NULL;
@@ -1997,9 +1903,9 @@ finish_command (const char *arg, int from_tty)
 
   tp = inferior_thread ();
 
-  sm = new_finish_command_fsm (command_interp ());
+  sm = new finish_command_fsm (command_interp ());
 
-  tp->thread_fsm = &sm->thread_fsm;
+  tp->thread_fsm = sm;
 
   /* Finishing from an inline frame is completely different.  We don't
      try to show the "return value" - no way to locate it.  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index 00eefabf6e..ee854f5422 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -2812,7 +2812,7 @@ clear_proceed_status_thread (struct thread_info *tp)
   if (!signal_pass_state (tp->suspend.stop_signal))
     tp->suspend.stop_signal = GDB_SIGNAL_0;
 
-  thread_fsm_delete (tp->thread_fsm);
+  delete tp->thread_fsm;
   tp->thread_fsm = NULL;
 
   tp->control.trap_expected = 0;
@@ -3726,8 +3726,7 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
 {
   if (ecs->event_thread != NULL
       && ecs->event_thread->thread_fsm != NULL)
-    thread_fsm_clean_up (ecs->event_thread->thread_fsm,
-			 ecs->event_thread);
+    ecs->event_thread->thread_fsm->clean_up (ecs->event_thread);
 
   if (!non_stop)
     {
@@ -3739,7 +3738,7 @@ clean_up_just_stopped_threads_fsms (struct execution_control_state *ecs)
 	    continue;
 
 	  switch_to_thread (thr);
-	  thread_fsm_clean_up (thr->thread_fsm, thr);
+	  thr->thread_fsm->clean_up (thr);
 	}
 
       if (ecs->event_thread != NULL)
@@ -3880,7 +3879,7 @@ fetch_inferior_event (void *client_data)
 	  struct thread_fsm *thread_fsm = thr->thread_fsm;
 
 	  if (thread_fsm != NULL)
-	    should_stop = thread_fsm_should_stop (thread_fsm, thr);
+	    should_stop = thread_fsm->should_stop (thr);
 	}
 
       if (!should_stop)
@@ -3889,16 +3888,13 @@ fetch_inferior_event (void *client_data)
 	}
       else
 	{
-	  int should_notify_stop = 1;
+	  bool should_notify_stop = true;
 	  int proceeded = 0;
 
 	  clean_up_just_stopped_threads_fsms (ecs);
 
 	  if (thr != NULL && thr->thread_fsm != NULL)
-	    {
-	      should_notify_stop
-		= thread_fsm_should_notify_stop (thr->thread_fsm);
-	    }
+	    should_notify_stop = thr->thread_fsm->should_notify_stop ();
 
 	  if (should_notify_stop)
 	    {
@@ -8009,11 +8005,11 @@ print_stop_event (struct ui_out *uiout)
 
   tp = inferior_thread ();
   if (tp->thread_fsm != NULL
-      && thread_fsm_finished_p (tp->thread_fsm))
+      && tp->thread_fsm->finished_p ())
     {
       struct return_value_info *rv;
 
-      rv = thread_fsm_return_value (tp->thread_fsm);
+      rv = tp->thread_fsm->return_value ();
       if (rv != NULL)
 	print_return_value (uiout, rv);
     }
diff --git a/gdb/mi/mi-interp.c b/gdb/mi/mi-interp.c
index d4baa48521..2b8e59d899 100644
--- a/gdb/mi/mi-interp.c
+++ b/gdb/mi/mi-interp.c
@@ -632,11 +632,11 @@ mi_on_normal_stop_1 (struct bpstats *bs, int print_frame)
       tp = inferior_thread ();
 
       if (tp->thread_fsm != NULL
-	  && thread_fsm_finished_p (tp->thread_fsm))
+	  && tp->thread_fsm->finished_p ())
 	{
 	  enum async_reply_reason reason;
 
-	  reason = thread_fsm_async_reply_reason (tp->thread_fsm);
+	  reason = tp->thread_fsm->async_reply_reason ();
 	  mi_uiout->field_string ("reason", async_reason_lookup (reason));
 	}
       print_stop_event (mi_uiout);
diff --git a/gdb/thread-fsm.c b/gdb/thread-fsm.c
deleted file mode 100644
index 1f0eed71a0..0000000000
--- a/gdb/thread-fsm.c
+++ /dev/null
@@ -1,109 +0,0 @@
-/* Thread command's finish-state machine, for GDB, the GNU debugger.
-   Copyright (C) 2015-2019 Free Software Foundation, Inc.
-
-   This file is part of GDB.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 3 of the License, or
-   (at your option) any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
-
-#include "defs.h"
-#include "thread-fsm.h"
-
-/* See thread-fsm.h.  */
-
-void
-thread_fsm_ctor (struct thread_fsm *self, struct thread_fsm_ops *ops,
-		 struct interp *cmd_interp)
-{
-  self->command_interp = cmd_interp;
-  self->finished = 0;
-  self->ops = ops;
-}
-
-/* See thread-fsm.h.  */
-
-void
-thread_fsm_delete (struct thread_fsm *self)
-{
-  if (self != NULL)
-    {
-      if (self->ops->dtor != NULL)
-	self->ops->dtor (self);
-      xfree (self);
-    }
-}
-
-/* See thread-fsm.h.  */
-
-void
-thread_fsm_clean_up (struct thread_fsm *self, struct thread_info *thread)
-{
-  if (self->ops->clean_up != NULL)
-    self->ops->clean_up (self, thread);
-}
-
-/* See thread-fsm.h.  */
-
-int
-thread_fsm_should_stop (struct thread_fsm *self, struct thread_info *thread)
-{
-  return self->ops->should_stop (self, thread);
-}
-
-/* See thread-fsm.h.  */
-
-struct return_value_info *
-thread_fsm_return_value (struct thread_fsm *self)
-{
-  if (self->ops->return_value != NULL)
-    return self->ops->return_value (self);
-  return NULL;
-}
-
-/* See thread-fsm.h.  */
-
-void
-thread_fsm_set_finished (struct thread_fsm *self)
-{
-  self->finished = 1;
-}
-
-/* See thread-fsm.h.  */
-
-int
-thread_fsm_finished_p (struct thread_fsm *self)
-{
-  return self->finished;
-}
-
-/* See thread-fsm.h.  */
-
-enum async_reply_reason
-thread_fsm_async_reply_reason (struct thread_fsm *self)
-{
-  /* If we didn't finish, then the stop reason must come from
-     elsewhere.  E.g., a breakpoint hit or a signal intercepted.  */
-  gdb_assert (thread_fsm_finished_p (self));
-
-  return self->ops->async_reply_reason (self);
-}
-
-/* See thread-fsm.h.  */
-
-int
-thread_fsm_should_notify_stop (struct thread_fsm *self)
-{
-  if (self->ops->should_notify_stop != NULL)
-    return self->ops->should_notify_stop (self);
-  return 1;
-}
diff --git a/gdb/thread-fsm.h b/gdb/thread-fsm.h
index 3f63dbd264..57837bfdfb 100644
--- a/gdb/thread-fsm.h
+++ b/gdb/thread-fsm.h
@@ -30,34 +30,24 @@ struct thread_fsm_ops;
 
 struct thread_fsm
 {
-  /* Pointer of the virtual table of methods.  */
-  struct thread_fsm_ops *ops;
+  explicit thread_fsm (struct interp *cmd_interp)
+    : command_interp (cmd_interp)
+  {
+  }
 
-  /* Whether the FSM is done successfully.  */
-  int finished;
-
-  /* The interpreter that issued the execution command that caused
-     this thread to resume.  If the top level interpreter is MI/async,
-     and the execution command was a CLI command (next/step/etc.),
-     we'll want to print stop event output to the MI console channel
-     (the stepped-to line, etc.), as if the user entered the execution
-     command on a real GDB console.  */
-  struct interp *command_interp;
-};
-
-/* The virtual table of a thread_fsm.  */
-
-struct thread_fsm_ops
-{
   /* The destructor.  This should simply free heap allocated data
      structures.  Cleaning up target resources (like, e.g.,
      breakpoints) should be done in the clean_up method.  */
-  void (*dtor) (struct thread_fsm *self);
+  virtual ~thread_fsm () = default;
+
+  DISABLE_COPY_AND_ASSIGN (thread_fsm);
 
   /* Called to clean up target resources after the FSM.  E.g., if the
      FSM created internal breakpoints, this is where they should be
      deleted.  */
-  void (*clean_up) (struct thread_fsm *self, struct thread_info *thread);
+  virtual void clean_up (struct thread_info *thread)
+  {
+  }
 
   /* Called after handle_inferior_event decides the target is done
      (that is, after stop_waiting).  The FSM is given a chance to
@@ -66,50 +56,58 @@ struct thread_fsm_ops
      should be re-resumed.  This is a good place to cache target data
      too.  For example, the "finish" command saves the just-finished
      function's return value here.  */
-  int (*should_stop) (struct thread_fsm *self, struct thread_info *thread);
+  virtual bool should_stop (struct thread_info *thread) = 0;
 
   /* If this FSM saved a function's return value, you can use this
      method to retrieve it.  Otherwise, this returns NULL.  */
-  struct return_value_info *(*return_value) (struct thread_fsm *self);
-
-  /* The async_reply_reason that is broadcast to MI clients if this
-     FSM finishes successfully.  */
-  enum async_reply_reason (*async_reply_reason) (struct thread_fsm *self);
+  virtual struct return_value_info *return_value ()
+  {
+    return nullptr;
+  }
+
+  enum async_reply_reason async_reply_reason ()
+  {
+    /* If we didn't finish, then the stop reason must come from
+       elsewhere.  E.g., a breakpoint hit or a signal intercepted.  */
+    gdb_assert (finished_p ());
+    return do_async_reply_reason ();
+  }
 
   /* Whether the stop should be notified to the user/frontend.  */
-  int (*should_notify_stop) (struct thread_fsm *self);
-};
-/* Initialize FSM.  */
-extern void thread_fsm_ctor (struct thread_fsm *self,
-			     struct thread_fsm_ops *ops,
-			     struct interp *cmd_interp);
-
-/* Calls the FSM's dtor method, and then frees FSM.  */
-extern void thread_fsm_delete (struct thread_fsm *fsm);
-
-/* Calls the FSM's clean_up method.  */
-extern void thread_fsm_clean_up (struct thread_fsm *fsm,
-				 struct thread_info *thread);
+  virtual bool should_notify_stop ()
+  {
+    return true;
+  }
 
-/* Calls the FSM's should_stop method.  */
-extern int thread_fsm_should_stop (struct thread_fsm *fsm,
-				   struct thread_info *thread);
+  void set_finished ()
+  {
+    finished = true;
+  }
 
-/* Calls the FSM's return_value method.  */
-extern struct return_value_info *
-  thread_fsm_return_value (struct thread_fsm *fsm);
+  bool finished_p () const
+  {
+    return finished;
+  }
 
-/* Marks the FSM as completed successfully.  */
-extern void thread_fsm_set_finished (struct thread_fsm *fsm);
+  /* The interpreter that issued the execution command that caused
+     this thread to resume.  If the top level interpreter is MI/async,
+     and the execution command was a CLI command (next/step/etc.),
+     we'll want to print stop event output to the MI console channel
+     (the stepped-to line, etc.), as if the user entered the execution
+     command on a real GDB console.  */
+  struct interp *command_interp = nullptr;
 
-/* Returns true if the FSM completed successfully.  */
-extern int thread_fsm_finished_p (struct thread_fsm *fsm);
+protected:
 
-/* Calls the FSM's reply_reason method.  */
-extern enum async_reply_reason
-  thread_fsm_async_reply_reason (struct thread_fsm *fsm);
+  /* Whether the FSM is done successfully.  */
+  bool finished = false;
 
-/* Calls the FSM's should_notify_stop method.  */
-extern int thread_fsm_should_notify_stop (struct thread_fsm *self);
+  /* The async_reply_reason that is broadcast to MI clients if this
+     FSM finishes successfully.  */
+  virtual enum async_reply_reason do_async_reply_reason ()
+  {
+    gdb_assert_not_reached (_("should not call async_reply_reason here"));
+  }
+};
 
 #endif /* THREAD_FSM_H */
diff --git a/gdb/thread.c b/gdb/thread.c
index e026d7d766..e6b56fa08c 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -170,8 +170,8 @@ thread_cancel_execution_command (struct thread_info *thr)
 {
   if (thr->thread_fsm != NULL)
     {
-      thread_fsm_clean_up (thr->thread_fsm, thr);
-      thread_fsm_delete (thr->thread_fsm);
+      thr->thread_fsm->clean_up (thr);
+      delete thr->thread_fsm;
       thr->thread_fsm = NULL;
     }
 }
