30 January 2025

I edited the following on StackOverflow in an attempt for it to pass review so I could answer the question. The question had many comments saying it was bad or inappropriate. Others accepted that after my edit, it was valid. Since it has been a couple hours, I'm posting my answer after the quoted section. 

Would someone please review the procedure below on how the compiler works with polymorphism and let me know if there's anything incorrect? Thanks!

Step 1: Creating and maintaining the vtable from the most base class to the most derived class based on the following rules:

If a function is declared virtual in a class (with or without ancestors), it will appear in the vtable.

If a derived class defines a function with the same name and signature as a virtual function in its ancestor, the derived class's version will replace the ancestor's version in the vtable, regardless of whether the derived class uses the override keyword. Otherwise, the derived function does not replace the base class's virtual function in the vtable. Instead, it is treated as a new function.

If a function in the derived class is declared as override in the derived class, then there should be a virtual function with the same name and signature in its ancestor class.

Non-virtual functions do not participate in the vtable and are resolved at compile time based on the static type.

Step 2: If we attempt to call a function (say, func()) using a pointer created by A* ptr_A = new C()

If the function is non-virtual in the inheritance hierarchy between A and C, ptr_A->func() calls that are declared in A based on static type.

If the function is virtual: At compile time, the compiler determines which functions are accessible via a static type of ptr_A (i.e., all public/protected virtual functions declared in A are accessible).

If the function is accessible at compile time, we search for the function name in the vtable and determine which specific virtual function implementation is executed.

Step 3: If we dynamic cast a pointer like this: C* ptr_C = dynamic_cast<C*>(ptr_A);

First, check if ptr_C is null. If (1) the C is not the descendant of A or if (2) all ancestors of C are non-polymorphic, then dynamic_cast returns null.

If ptr_C is not null, at runtime, dynamic_cast grants access to all public/protected functions declared in C, and those inherited from C's ancestors (A and B).

My Answer

The procedure has some misunderstandings, so it is good you asked. Rather than go line by line, let me simply explain it.

Virtual / Override / VTables

  1. Any class with virtual functions has its own vtable holding the address of the virtual functions.
  2. A base class with virtual functions has a vtable - which holds the addresses of its functions. A hidden pointer in the base class holds the address of the vtable.
  3. A derived class has its own vtable which contains pointers to the virtual function from the base class and the virtual functions that override a base class function.
  1. ClassFunctionPoints to
    Basevirtual aBase::a
    virtual bBase::b
    Deriveda overrideDerived::a
    no bBase::b
    virtual cDerived::c
    This pattern continues if a class derives from Derived. New virtual functions are appended to the table; any override functions replace those from Derived or Base.

Operation

  1. Non-virtual functions are straightforward. They are called directly with no lookup in the vtable. The same is true of virtual functions, which are directly called.
  2. The vtable is used only for virtual functions, which are called using a pointer to a class.
  3. The Base is simple, with virtual functions called through the Base vtable. The compiler doesn't know whether the pointer is to Base or Derived.
  4. Similarly, a Derived pointer uses its vtable for calls.
  5. Now it gets tricky with a pointer from Derived in a Base pointer. The Derived pointer in Base* points to Derived's vtable.
  6. When this Base pointer is called for function a, the compiler looks (this is at run-time) in the vtable for the address and retrieves the address of Derived::a.
  7. When this Base pointer is called for function b, the compiler looks (this is at run-time) in the vtable for the address and retrieves the address of Base::b.
  8. In short, virtual functions for any class are found through that class's vtable.

Some Code to Illustrate

class A {
public:
   virtual ~A() {}
   virtual void meow() { }
};

class B : public A {
public:
   virtual void meow() override { }
   virtual void woof() { }
};

class C : public B {
public:
   virtual void moo() { }
};

int main() {
   A* ptr_AC = new C();

   ptr_AC->woof(); // calls B::woof
   ptr_AC->moo();  // calls C::moo
   
   C* ptr_C = dynamic_cast<C*>(ptr_A);

   ptr_C->foo();
}

 

05 July 2020

SRC2 - Explicit Steering - Wheel Speed

SRC2 Rover
This fourth post about the qualifying round of the NASA Space Robotics Challenge - Phase 2 (SRC2) addresses the speed of the wheels of the rover, shown to the right, under the various possible motions. The rover uses Explicit Four Wheel Steering which allows the orientation of the wheels to be independently changed. The second and third posts explored the geometry to determine the position of the wheels for a turn, pivoting in place, and crab / straight movement. See the first post for the basics of the competition. 

Wheel Orientation

The orientation of the wheels on the rover determines the speed for the wheels. In straight or crab movement the speed is the same for all wheels. When turning, shown in the diagram below, the speeds are different for the inner and outer wheels. The requested overall speed of the rover, determined at the center of the rover, is used to calculate the inner and outer speeds. 



 Term        Description
 ICR Instantaneous Center of Rotation
 Rr Radius from ICR to center of rover
 RiRo Radius of  rover's inner (i) and outer (o) sides through ICR
 Wb, Wt Wheel base and wheel track of rover. Lengths are representative of actual size.
 WRi, WRo Radius of inner(i) and outer (o) wheels
  δiδo  Steering angle for inner (i) and outer (o) wheels

Visualize on the diagram three concentric circles drawn from the ICR. One circle passes through the center of the rover while the others pass through the inner and outer corners, or wheels, of the rover. The second post calculated the wheel's turning radius as:


The rover radius is Rr, the distance from the ICR to the center of the rover. 

The speed (Sr and turn radius of the rover determine the time (Tr) to complete a full circle, as shown in the first equation below. The next equation calculates the speed of either set of wheels (WR) using the circumference of the respective circles. Subsequently, that equation can be simplified as shown in the formulations that follow. 



Twist Calculation

The standard ROS movement command is the twist message. It contains two 3 dimensional vectors. One specifies the linear movement for the x, y, and z dimensions. The other specifies the orientation, also as x, y, and z, but meaning roll, pitch, and yaw respectively. 

The calculations for steering orientation and speed are all based on the radius of the turn. That turn radius needs to be calculated using the X velocity and the Yaw from the message. Recall from post three that turning during a crab movement is not under consideration so the linear Y value is ignored. 

Getting to how to do the calculation requires some interesting analysis but the final, actual calculation is extremely simple. The starting point is the Yaw in radians / second. The first equation determines the time it would take to turn a full 2𝜋 radians at the Yaw rate. Or, how long to traverse a full circle. 



Next, that time is used to determine the circumference of the circle using the X speed. Knowing the circumference the radius is determined. The equations show the individual steps but then combine them to reduce them to a simple calculation. Everything should reduce to such simplicity. Note that dimensional units are included to assure the final units are valid.

I edited the following on StackOverflow in an attempt for it to pass review so I could answer the question. The question had many comments s...