Skip to content

Commit 92b2c14

Browse files
committed
add cpp cmd ownership page
1 parent 25cd5af commit 92b2c14

File tree

2 files changed

+142
-0
lines changed

2 files changed

+142
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
C++ Command Ownership Model
2+
===========================
3+
4+
C++ Ownership Scope Semantics
5+
-----------------------------
6+
7+
Unlike languages (such as Java and Python) that hide memory management from the programmer, C++ programmers must be conscious of memory management and object lifetimes.
8+
9+
When allocating on the stack, as following, the object is destroyed when it goes out of scope. In this case, ``x`` is destroyed when ``MyFunction`` returns:
10+
11+
.. code-block:: c++
12+
13+
int* MyFunction() {
14+
int x = 3;
15+
// ...
16+
return &x; // invalid pointer -- using it will crash/"segfault"!!!
17+
} // x is destroyed here!
18+
19+
Thus, it is important to store objects in the correct scope so they aren't destroyed while they're used -- this causes a whole category of C++ bugs called "use after free".
20+
21+
Allocating on the heap (with ``new``) persists the object beyond the variable scope, but then the object must be destroyed manually, which is even more difficult to do at the right time: this can cause "use after free" bugs (if the object is destroyed too early) as well as memory leaks (if the object is not destroyed).
22+
23+
In summary, each object should always be _owned_ by a scope that dictates its lifetime and is responsible for automatically destroying it.
24+
25+
26+
Ownership of Command Objects
27+
----------------------------
28+
29+
The C++ Command-based framework often uses two types with vastly different ownership semantics:
30+
31+
- ``Command*`` is a raw pointer, non-owning: the command object is owned elsewhere.
32+
- ``CommandPtr`` is an "smart" owning pointer (wrapper around ``std::unique_ptr``): whatever owns the ``CommandPtr`` transitively owns the command object.
33+
34+
In other words, functions that take/return a ``CommandPtr`` take/return _ownership_ of the command object, as opposed to functions taking/returning a ``Command*`` that don't transfer ownership.
35+
36+
For example, the :ref:`trigger bindings <../binding-commands-to-triggers>` have overloads for both ``Command*`` and ``CommandPtr``.
37+
38+
Command*: Non-Owning
39+
^^^^^^^^^^^^^^^^^^^^
40+
41+
Here, the command objects are defined as variables of the ``RobotContainer`` class, and therefore are owned by the ``RobotContainer`` object and exist as long as the robot code is running. The variables can be of a ``Command`` subclass (such as ``InstantCommand`` in the case of ``m_driveHalfSpeed`` and ``m_driveHalfSpeed``), or as ``CommandPtr`` (as in ``m_spinUpShooter`` and ``m_stopShooter``).
42+
43+
.. tab-set::
44+
45+
.. tab-item:: Command Subclass
46+
:sync: tabcode-subclass
47+
48+
.. remoteliteralinclude:: https://raw.githubusercontent.com/wpilibsuite/allwpilib/v2024.1.1-beta-3/wpilibcExamples/src/main/cpp/examples/StateSpaceDifferentialDriveSimulation/include/RobotContainer.h
49+
:language: cpp
50+
:lines: 26, 34, 43-46
51+
:linenos:
52+
:lineno-start: 26
53+
54+
.. tab-item:: CommandPtr
55+
:sync: tabcode-ptr
56+
57+
.. remoteliteralinclude:: https://raw.githubusercontent.com/wpilibsuite/allwpilib/v2024.1.1-beta-3/wpilibcExamples/src/main/cpp/examples/Frisbeebot/include/RobotContainer.h
58+
:language: cpp
59+
:lines: 25, 32, 41-49
60+
:linenos:
61+
:lineno-start: 25
62+
63+
To get a ``Command*``, use ``&`` (address-of operator) in case of a ``Command`` subclass or ``.get()`` in case of a ``CommandPtr``, and pass it the trigger binding method (such as ``OnTrue``):
64+
65+
.. tab-set::
66+
67+
.. tab-item:: Command Subclass
68+
:sync: tabcode-subclass
69+
70+
.. remoteliteralinclude:: https://raw.githubusercontent.com/wpilibsuite/allwpilib/v2024.1.1-beta-3/wpilibcExamples/src/main/cpp/examples/StateSpaceDifferentialDriveSimulation/cpp/RobotContainer.cpp
71+
:language: cpp
72+
:lines: 50-58
73+
:linenos:
74+
:lineno-start: 50
75+
76+
.. tab-item:: CommandPtr
77+
:sync: tabcode-ptr
78+
79+
.. remoteliteralinclude:: https://raw.githubusercontent.com/wpilibsuite/allwpilib/v2024.1.1-beta-3/wpilibcExamples/src/main/cpp/examples/Frisbeebot/cpp/RobotContainer.cpp
80+
:language: cpp
81+
:lines: 22-31
82+
:linenos:
83+
:lineno-start: 22
84+
85+
86+
Since the command was passed as a ``Command*``, ownership is not transferred and the program relies on the command being owned in an appropriate scope. If the command object were to be defined in a different scope and get destroyed, this would be a use-after-free and the program would crash or otherwise misbehave ("Undefined Behavior").
87+
88+
CommandPtr: Owning
89+
^^^^^^^^^^^^^^^^^^
90+
91+
Here, commands are defined as ``CommandPtr`` and _moved_ into the binding, ownership is passed to the scheduler.
92+
93+
.. rli:: https://github.com/wpilibsuite/allwpilib/raw/v2024.1.1-beta-4/wpilibcExamples\src\main\cpp\examples\Frisbeebot\cpp\RobotContainer.cpp
94+
:language: c++
95+
:lines: 22, 33-52
96+
:linenos:
97+
:lineno-start: 22
98+
99+
Note the calls to ``std::move`` that hint at the ownership move.
100+
101+
The ``shoot`` and ``stopFeeder`` variables will be destroyed when the function returns, but this isn't a problem because the object was ``std::move``d into the function. However, these variables are now in an invalid state and must not be used! Similar to use-after-free, using them would cause crashes or other undefined behavior: this is called use-after-move.
102+
103+
To avoid the risk of use-after-move and invalid variables, ``CommandPtr``s can also be passed inline:
104+
105+
.. rli:: https://github.com/wpilibsuite/allwpilib/raw/v2024.1.1-beta-4/wpilibcExamples/src/main/cpp/examples/Frisbeebot/cpp/RobotContainer.cpp
106+
:language: c++
107+
:lines: 22, 54-60
108+
:linenos:
109+
:lineno-start: 22
110+
111+
It's also possible to convert ``Command`` subclasses to ``CommandPtr`` using ``.ToPtr()``:
112+
113+
.. rli:: https://github.com/wpilibsuite/allwpilib/raw/v2024.1.1-beta-4/wpilibcExamples/src/main/cpp/examples/GearsBot/cpp/RobotContainer.cpp
114+
:language: c++
115+
:lines: 37
116+
:linenos:
117+
:lineno-start: 37
118+
119+
Ownership in Compositions
120+
-------------------------
121+
122+
As described in :ref:`/docs/software/commandbased/command-compositions`, command instances that have been passed to a command composition cannot be independently scheduled or passed to a second command composition. In C++, this interacts nicely with the ownership model: each composition owns its components! This way, double-composition bugs are nearly inexistent in C++ (whereas they pose a common error in Java).
123+
124+
Therefore, compositions only take ``CommandPtr``s and not ``Command*``:
125+
126+
.. rli:: https://github.com/wpilibsuite/allwpilib/raw/v2024.1.1-beta-4/wpilibcExamples/src/main/cpp/examples/MecanumControllerCommand/cpp/RobotContainer.cpp
127+
:language: c++
128+
:lines: 112-121
129+
:linenos:
130+
:lineno-start: 112
131+
132+
Ownership of Default Commands
133+
-----------------------------
134+
135+
All default commands are owned by the scheduler, therefore, ``SetDefaultCommand`` only takes a ``CommandPtr`` and not a ``Command*``:
136+
137+
.. rli:: https://github.com/wpilibsuite/allwpilib/raw/v2024.1.1-beta-4/wpilibcExamples/src/main/cpp/examples/ArmBot/cpp/RobotContainer.cpp
138+
:language: c++
139+
:lines: 22-27
140+
:linenos:
141+
:lineno-start: 22

source/docs/software/commandbased/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ For a collection of example projects using the command-based framework, see :ref
1616
structuring-command-based-project
1717
organizing-command-based
1818
command-scheduler
19+
cpp-command-ownership
1920
cpp-command-discussion
2021
pid-subsystems-commands
2122
profile-subsystems-commands

0 commit comments

Comments
 (0)