我是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的好方法。
初始化这些指针成员的正确方法是什么?
答案 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::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
将具有机箱和配置文件控制器的非指针成员。
另一种避免使用指针的方法是使用std::any
对两个成员控制器进行类型擦除:如果这是工厂返回的内容,则您将不会在基类上使用虚拟方法,但是,如果您的代码知道应该应该获取的控制器类型,则它可以通过类型安全的方式从std::any
获取它。