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::ChassisControllerIntegrated
和 okapi::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>
可以容纳 int
或 double
,但不能同时容纳两者;它占用的存储空间比不同类型的最大存储空间多一点。这样您就可以完全避免使用指针,DeviceController
底盘和配置文件控制器将具有非指针成员。
另一种避免使用指针的方法是使用 std::any
类型擦除两个成员控制器:如果那是工厂 returns,您将无法使用虚拟的好处基础 class 上的方法,但是如果您的代码知道它 应该 获取的控制器类型 - 它可以以类型安全的方式从 std::any
.
我是 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::ChassisControllerIntegrated
和 okapi::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>
可以容纳 int
或 double
,但不能同时容纳两者;它占用的存储空间比不同类型的最大存储空间多一点。这样您就可以完全避免使用指针,DeviceController
底盘和配置文件控制器将具有非指针成员。
另一种避免使用指针的方法是使用 std::any
类型擦除两个成员控制器:如果那是工厂 returns,您将无法使用虚拟的好处基础 class 上的方法,但是如果您的代码知道它 应该 获取的控制器类型 - 它可以以类型安全的方式从 std::any
.