简体   繁体   中英

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?

To make your pointers live as long as the object of DriverController live you can use std::unique_ptr instead of raw pointers.

And as for the construction of the chassisController, since it is not copyable the solution for this can be done using C++17 copy-elision:

chassisController = std::unique_ptr<okapi::ChassisControllerIntegrated> { new okapi::ChassisControllerIntegrated( okapi::ChassisControllerFactory::create( ...) )};

same thing for the profiler

Anyway, as other people commented and as the other factory is using references/values and not pointers, you might be better of storing both the controller and profiler as values. But in order to store them as values you must initialize them in the contstuctor's initializer list as:

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
    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)}
  {
   // no need to do anything in the body
  }  

Also very important detail is that the order of the data members defined needs to be the same as the order of the initialization in the constructor, ie since we use chassisController to initialize the chassisMotionProfiler , the chassisController needs to be declared before the chassisMotionProfiler

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_ptr s, 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, eg 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM