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

C++ proper pointer member initialization

我是 C++ 新手,有 Java 背景。我有一个 class 原型,它设置了两个私有指针对象成员。

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;

现在,在这个 class 的构造函数中,我使用我正在使用的 API 提供给我的工厂设计模式来初始化这两个变量。这是初始化这些 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;
  }

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

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

要使您的指针与 DriverController 的对象一样长,您可以使用 std::unique_ptr 而不是原始指针。

关于 chassisController 的构造,由于它不可复制,因此可以使用 C++17 复制省略来完成此解决方案:

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

探查器也是如此

无论如何,正如其他人评论的那样,并且由于其他工厂使用的是 references/values 而不是指针,您最好将控制器和分析器都存储为值。但是为了将它们存储为值,您必须在构造函数的初始化列表中将它们初始化为:

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

之前声明

我首先要说这段代码看起来非常 Java-esque:对象是 "doers of things"(控制器控制,配置文件配置文件) - 为什么不只是控制和配置文件,当你需要?这可能排除了对工厂的需求。

但忽略这一点,假设你确实需要这些点:

让您的工厂 return unique_ptr 具有自定义删除器

正如评论者所说,你们工厂的行为很奇怪。它们 似乎 分别是 return 类型 okapi::ChassisControllerIntegratedokapi::AsyncMotionProfileController 的值(或可转换为这两种类型的值)- 一旦您获取了它们的地址。但这意味着工厂总是 return 相同的类型,这首先违背了拥有工厂的目的(​​工厂可以通过指向基类的指针 return 某些层次结构中的任何类型的值class)。如果是这种情况,那么,确实,正如@me所说的那样——你创建的对象在离开构造函数的范围时将被销毁。

如果你的工厂要 return 指向这两个 classes 的指针,代码就可以工作,但这是个坏主意,因为你需要正确地取消分配销毁的两个指向对象(甚至将它们发送到工厂销毁)。

@BobBills 提出了一种避免这种情况的方法,即将创建的两个指针包装在 std::unique_ptr 中。这工作正常,但前提是您可以天真地解除分配它们。

我的建议是您自己制作 工厂 return 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 代码可以提前知道所有不同类型的底盘控制器和配置文件控制器,您确实可以让您的工厂 return 一个值 - 一个 std::variant,它可以保存几种固定类型中的任何一种的单个值,例如std::variant<int, double> 可以容纳 intdouble,但不能同时容纳两者;它占用的存储空间比不同类型的最大存储空间多一点。这样您就可以完全避免使用指针,DeviceController 底盘和配置文件控制器将具有非指针成员。

另一种避免使用指针的方法是使用 std::any 类型擦除两个成员控制器:如果那是工厂 returns,您将无法使用虚拟的好处基础 class 上的方法,但是如果您的代码知道它 应该 获取的控制器类型 - 它可以以类型安全的方式从 std::any.