Search code examples
c++apipointersunique-ptrfactory-pattern

C++ proper pointer member initialization


I'm new to C++ here, coming from a Java background. I have a class prototype that sets up two private pointer object members.

class DriveController : Controller {

public:

DriveController(
    int8_t portTL_, int8_t portTR_, int8_t portBL_, int8_t portBR_, 
    double wheelSize_, double baseSize_);

private:
// Internal chassis controller
okapi::ChassisControllerIntegrated *chassisController;
okapi::AsyncMotionProfileController *chassisMotionProfiler;

Now, in my constructor for this class, I initialize these two variables using a factory design pattern that is provided to me by the API that I am using. This is the only real way to initialize these classes.

DriveController::DriveController(
    int8_t portTL_, int8_t portTR_, int8_t portBL_, int8_t portBR_, 
    double wheelSize, double baseSize) 
{
    // Initialize port definitions
    portTL = portTL_;
    portTR = portTR_;
    portBL = portBL_;
    portBR = portBR_;

    // Create chassis
    auto chassis = okapi::ChassisControllerFactory::create(
        {portTL, portBL}, // Left motors
        {portTR, portBR}, // Right motors
        okapi::AbstractMotor::gearset::red, // torque gearset
        {wheelSize, baseSize} // wheel radius, base width
    );
    chassisController = &chassis;

    auto profiler = okapi::AsyncControllerFactory::motionProfile(
        1.0, 2.0, 10.0, *chassisController);
    chassisMotionProfiler = &profiler;
  }

I know I'm doing something wrong with the allocation of memory here, because when I try to access these member pointers in later functions that are called, the program errors with a "Memory Permission Error". I was looking into using unique_ptr to store the objects, as they manage life cycle well, however, since the only way to create the objects is through the factory initializer, I haven't been able to find a good way to construct the unique_ptr.

What is the proper way to initialize these pointer members?


Solution

  • I'll first say this code looks very Java-esque: Objects which are "doers of things" (a controller which controls, a profile which profiles) - why not just control and profile when you need to? That might preclude the need for the factories.

    But ignoring this point, and assuming you do really need those points:

    Have your factories return unique_ptr's with custom deleters

    As commenters suggest, your factories act weird. They seem to be returning values of type okapi::ChassisControllerIntegrated and okapi::AsyncMotionProfileController respectively (or types convertible to those two) - once you take their addresses. But that means the factories always return the same type, which defeats the purpose of having a factory in the first place (a factory can return a value of any type within some hierarchy via a pointer to the base class). If this were the case then, indeed, as @me' said - your created objects would be destroyed when leaving the constructor's scope.

    If your factories were to return pointers to these two classes, the code would work, but it would be kind of a bad idea, since you would need to properly de-allocate the two pointed-to objects on destruction (or even send them to the factories for destruction).

    @BobBills suggests one way of avoiding that, which is wrapping the two created pointers in std::unique_ptr's. This works fine, but only if you can deallocate them naively.

    What I suggest is that you make the factories themselves return std::unique_ptrs, with the specific deleter function they need you to use. That you will really not have to worry about deletion at all - and neither will any other code using the factories.

    The constructor code will be:

    DriveController::DriveController(
        int8_t portTL_, int8_t portTR_, int8_t portBL_, int8_t portBR_, 
        double wheelSize, double baseSize)
    :
        portTL{ portTL_}, portTR{ portTR_},
        portBL{ portBL_}, portBR{ portBR_},
    
        chassisController { 
            okapi::ChassisControllerFactory::create(
                {portTL, portBL}, // Left motors
                {portTR, portBR}, // Right motors
                okapi::AbstractMotor::gearset::red, // torque gearset
                {wheelSize, baseSize} // wheel radius, base width
            )
        },
    
        chassisMotionProfiler { 
            okapi::AsyncControllerFactory::motionProfile(
            1.0, 2.0, 10.0, chassisController)
        }
    { }  
    

    (the same as with @BobBills ' solution) - and the benefit is that the destructor can safely be assumed to be trivial:

    DriveController::~DriveController() = default;
    

    Consider non-pointer-based alternatives

    If your DeviceController code can know in advance all the different types of chassis controllers and profile controllers, you can indeed have your factory return a value - an std::variant, which can hold a single value of any of several fixed types, e.g. std::variant<int, double> can hold either an int or a double, but not both; and it takes up storage which is a bit more than the maximum storage over the different types. That way you can avoid pointers altogether, and DeviceController will have non-pointer members for the chassis and profile controllers.

    Another way to avoid pointer use is to type-erase the two member controllers, using std::any: If that's what the factory returns, you won't have the benefit of using virtual methods on the base class, but if you have code which knows what controller type it should get - it can obtain it, in a type-safe manner, from an std::any.