C ++适当的指针成员初始化

时间:2018-10-13 04:59:13

标签: c++ api pointers unique-ptr factory-pattern

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

我知道我在这里的内存分配有问题,因为当我尝试在后来调用的函数中访问这些成员指针时,程序错误并显示“ Memory Permission Error”。我一直在研究使用unique_ptr来存储对象,因为它们可以很好地管理生命周期,但是,由于创建对象的唯一方法是通过工厂初始化程序,因此,我一直没有找到构造unique_ptr的好方法。

初始化这些指针成员的正确方法是什么?

2 个答案:

答案 0 :(得分:1)

要使指针与DriverController的对象一样有效,可以使用std::unique_ptr代替原始指针。

对于到底盘控制器的构造,由于它是不可复制的,因此可以使用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

前声明

答案 1 :(得分:1)

我首先要说的是,这段代码看起来很像Java:对象是“事物的执行者”(一个控制器来控制,一个配置文件来配置)-为什么不只在需要时进行控制和配置呢?这样可能就不需要工厂了。

但是忽略这一点,并假设您确实需要这些点:

让您的工厂使用自定义删除器返回unique_ptr

正如评论员所建议,您的工厂表现怪异。一旦您获取了它们的地址,它们似乎分别返回okapi::ChassisControllerIntegratedokapi::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>可以容纳intdouble,但不能同时容纳两者。并且占用的存储空间比不同类型的最大存储空间要大一些。这样,您可以完全避免使用指针,并且DeviceController将具有机箱和配置文件控制器的非指针成员。

另一种避免使用指针的方法是使用std::any对两个成员控制器进行类型擦除:如果这是工厂返回的内容,则您将不会在基类上使用虚拟方法,但是,如果您的代码知道应该应该获取的控制器类型,则它可以通过类型安全的方式从std::any获取它。