C ++ правильная инициализация члена указателя - PullRequest
0 голосов
/ 13 октября 2018

Я новичок в C ++, пришедший из Java-среды.У меня есть прототип класса, который устанавливает два закрытых члена объекта указателя.

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;

Теперь в моем конструкторе для этого класса я инициализирую эти две переменные, используя шаблон проектирования фабрики, предоставленный мне API, который я использую.Это единственный реальный способ инициализации этих классов.

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;
  }

Я знаю, что я делаю что-то не так с распределением памяти здесь, потому что, когда я пытаюсь получить доступ к указателям на элементы в последующих вызываемых функциях, программа выдает ошибку «Ошибка разрешения памяти»,Я изучал использование unique_ptr для хранения объектов, поскольку они хорошо управляют жизненным циклом, однако, поскольку единственный способ создать объекты - это через фабричный инициализатор, я не смог найти хороший способ для создания unique_ptr.

Как правильно инициализировать эти элементы указателя?

Ответы [ 2 ]

0 голосов
/ 13 октября 2018

Сначала я скажу, что этот код выглядит очень Java-esque: объекты, которые являются «творцами вещей» (контроллер, который контролирует, профиль, который профилирует) - почему бы не просто контролировать и профилировать, когда это необходимо?Это может исключить необходимость в фабриках.

Но игнорируя эту точку и предполагая, что вам действительно нужны эти точки:

Ваши фабрики возвращают unique_ptr с пользовательскими удалителями

Как предполагают комментаторы, ваши фабрики ведут себя странно.Они кажутся возвращающими значения типа okapi::ChassisControllerIntegrated и okapi::AsyncMotionProfileController соответственно (или типы, конвертируемые в эти два) - как только вы берете их адреса.Но это означает, что фабрики всегда возвращают один и тот же тип, что лишает цель иметь фабрику в первую очередь (фабрика может возвращать значение любого типа в некоторой иерархии через указатель на базовый класс).Если бы это было так, то, как сказал @me ', ваши созданные объекты были бы уничтожены при выходе из области видимости конструктора.

Если бы ваши фабрики возвращали указатели на эти два класса, код работал бы,но это была бы плохая идея, поскольку вам нужно было бы правильно отменить выделение двух указанных объектов при уничтожении (или даже отправить их на фабрики для уничтожения).

@ BobBills предлагает один из способовизбежать того, что оборачивает два созданных указателя в std::unique_ptr.Это работает нормально, но только если вы можете наивно их освободить.

Я предлагаю вам сделать так, чтобы сами фабрики возвращали std::unique_ptr с, с конкретнымиФункция удаления они нуждаются в вас, чтобы использовать.То, что вам действительно не придется беспокоиться об удалении вообще, равно как и любой другой код, использующий фабрики.

Код конструктора будет:

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)
    }
{ }  

(так же, как с @Решение BobBills) - и преимущество в том, что деструктор можно смело считать тривиальным:

DriveController::~DriveController() = default;

Рассмотрим альтернативы без указателей

Если ваш код DeviceController может знатьзаранее все различные типы контроллеров шасси и контроллеров профилей, вы действительно можете заставить ваше фабрику возвращать значение - std::variant, которое может содержать одно значение любого из нескольких фиксированных типов, например std::variant<int, double> может содержать int или double, но не оба;и он занимает память, которая немного больше максимальной памяти для разных типов.Таким образом, вы можете полностью избежать указателей, и DeviceController будет иметь элементы без указателей для контроллеров шасси и профилей.

Другой способ избежать использования указателей - стереть два контроллера-члена с помощью * 1044.*std::any: Если это то, что возвращает фабрика, вы не будете иметь преимущества от использования виртуальных методов в базовом классе, но если у вас есть код, который знает, какой тип контроллера он должен получить - он может получить его безопасным для типов способом из std::any.

0 голосов
/ 13 октября 2018

Чтобы ваши указатели работали до тех пор, пока объект DriverController жив, вы можете использовать std::unique_ptr вместо необработанных указателей.

А что касается конструкции chassisController, так как это не копируемое решение дляэто можно сделать, используя C ++ 17 copy-elision:

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

То же самое для профилировщика

В любом случае, как прокомментировали другие люди, а другая фабрика использует ссылки / значения ине указатели, лучше хранить контроллер и профилировщик как значения.Но для того, чтобы сохранить их в качестве значений, вы должны инициализировать их в списке инициализатора contstuctor следующим образом:

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
  }  

Также очень важной деталью является то, что порядок определенных членов данных должен быть таким же, как порядокинициализация в конструкторе, т. е. поскольку мы используем chassisController для инициализации chassisMotionProfiler, chassisController необходимо объявить до chassisMotionProfiler

...