OOP Constructors as Parameters

There is no need to use new here. Usage of new should be limited to low-level code only.
C++ Core Guidelines R.11: Avoid calling new and delete explicitly
The Leg and Body classes also violate the rule of 3/5/0 and will corrupt memory:
C++ Core Guidelines C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all

If you ever (explicitly or implicitly) make a copy of a Leg, for example, you're in for a bad time:

    Leg l1(0, 1, 2);
    Leg l2 = l1;
==1==ERROR: AddressSanitizer: attempting double-free on 0x6020000000d0 in thread T0:
    #0 0x7f83f4d72dd7 in operator delete(void*, unsigned long) (/opt/compiler-explorer/gcc-11.2.0/lib64/libasan.so.6+0xb3dd7)
    #1 0x401b05 in Leg::~Leg() /app/example.cpp:48
    #2 0x4015c4 in main /app/example.cpp:111
    #3 0x7f83f47760b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
    #4 0x4011fd in _start (/app/output.s+0x4011fd)

Avoid all these issues by just using “the rule of zero” (i.e. not doing any resource management in custom destructors at all):

const uint8_t NUM_LEGS = 2;

class Joint {
  public:
    int number;
    Joint(int number) : number(number) {
      Serial.print("Joint::Joint("); Serial.print(number); Serial.println(")");
    }
    ~Joint() {
      Serial.print("Joint::~Joint("); Serial.print(number); Serial.println(")");
    }
    void begin() {
        Serial.print("Joint::begin("); Serial.print(number); Serial.println(")");
    }
};

class Leg {
  private:
    Joint joint1;
    Joint joint2;
    Joint joint3;

  public:
    Leg(Joint joint1, Joint joint2, Joint joint3)
        : joint1(joint1), joint2(joint2), joint3(joint3) {
      Serial.print("Leg::Leg(");
      Serial.print(joint1.number); Serial.print(", ");
      Serial.print(joint2.number); Serial.print(", ");
      Serial.print(joint3.number); Serial.println(")");
    }

    void begin() {
      joint1.begin();
      joint2.begin();
      joint3.begin();
    }

    void nextStep() {
      Serial.print("Leg::nextStep(");
      Serial.print(joint1.number); Serial.print(", ");
      Serial.print(joint2.number); Serial.print(", ");
      Serial.print(joint3.number); Serial.println(")");
    }
};

class Body {
  public:
    Leg legs[NUM_LEGS] {
        {0, 1, 2},
        {3, 4, 5},
    };

    void begin() {
      for (Leg &l : legs)
        l.begin();
    }

    void nextStep() {
      for (Leg &l : legs)
        l.nextStep();
    }
};


void setup() {
  Serial.begin(115200);
  Serial.println("Creating Body");
  Body body;
  body.begin();
  body.nextStep();
  // here Body gets deleted as it's local to the function so you'll see messages from the destructors
  Serial.println("Deleting Body");
}

void loop() {}