在一项项目中,我遇到了一个有趣的问题,即在通过构造函数将一个对象传递给另一个对象时,如果传递的对象保证比接收方对象更长寿(以内存生命周期为衡量标准),则会出现问题。请注意,我仍在学习C++11/C++14的内部细节,因此我希望进行有益的讨论,以帮助我理解C++11/C++14风格语义下的内存管理和生命周期。
本问题的设置如下:
尽管将
虽然以上示例代码在功能上满足我的需求,但我感觉存在“代码异味”。具体来说,使用原始指针存储对
我探索的一个替代方法是使用共享指针传递对
这种方法的缺点是,除非事先存在一个
有没有一种方法可以表达这样一个事实,即
本问题的设置如下:
class TopLevelClass {
public:
void someMethod (int someValue) {
// Do some work
}
std::unique_ptr<Context> getContext () {
return std::make_unique<Context>(this);
}
};
class Context {
public:
Context (TopLevelClass* tlc) : _tlc(tlc) {}
void call (int value) {
// Perform some work and then call the top level class...
_tlc->someMethod(value);
}
protected:
TopLevelClass* _tlc;
};
尽管将
TopLevelClass
作为参数传递给 Context
类的 call
方法是一种可行的解决方案,但在我所阐述的场景中不可能实现:能够访问到 Context
对象的客户端代码可能无法访问 TopLevelClass
对象。虽然以上示例代码在功能上满足我的需求,但我感觉存在“代码异味”。具体来说,使用原始指针存储对
TopLevelClass
对象的引用不能表明 Context
类不负责管理此指针的生命周期(因为在这种情况下,TopLevelClass
肯定会比任何 Context
对象存在更长时间)。另外,在 C++11 中,我不愿意使用原始指针而不是智能指针(正如 Scott Meyer 在《Effective Modern C++》中建议的那样)。我探索的一个替代方法是使用共享指针传递对
TopLevelClass
的引用,并将该引用存储在 Context
类中作为共享指针。需要注意的是,TopLevelClass
必须按照以下方式继承自 std::enable_shared_from_this
:class TopLevelClass : public std::enable_shared_from_this<TopLevelClass> {
public:
// Same "someMethod(int)" as before...
std::unique_ptr<Context> getContext () {
return std::make_unique<Context>(shared_from_this());
}
};
class Context {
public:
Context (std::shared_ptr<TopLevelClass> tlc) : _tlc(tlc) {}
// Same "call(int)" as before...
protected:
std::shared_ptr<TopLevelClass> _tlc;
};
这种方法的缺点是,除非事先存在一个
TopLevelClass
的std::shared_ptr
,否则将抛出std::bad_weak_ptr
异常(更多信息请参见此帖子)。由于在我的情况下,代码中没有创建std::shared_ptr<TopLevelClass>
,所以我不能使用std::enable_shared_from_this<T>
方法:我只能返回TopLevelClass
的单个实例,使用static
原始指针,按照我的项目要求进行操作,如下所示。static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
有没有一种方法可以表达这样一个事实,即
Context
不负责管理其对TopLevelClass
实例的句柄,因为TopLevelClass
将保证比任何Context
对象都要长寿?如果能够避开这个问题而不过度复杂化上述设计的简洁性(即创建许多不同的类来绕过仅传递单个指针到Context
构造函数的方式),我也很乐意听取建议。谢谢您的帮助。
getTopLevelClass()
是有害的。从函数签名中你可能会期望它返回一个单例,但实际上它每次调用都会返回一个拥有原始指针的新对象,调用者必须记得删除它!请注意,以下翻译仅供参考,具体翻译应根据上下文和语境确定。 - Chris DrewTopLevelClass *getTopLevelClass() { static TopLevelClass tlc; return &tlc; }
。 - villintehaspamTopLevelClass
吗?如果不会,那么你可以编写static TopLevelClass* getTopLevelClass(){ static TopLevelClass tlc; return &tlc;}
,仍然实现相同的接口而不拥有原始指针。 - Chris Drew从子节点到父节点的反向链接可以安全地实现为原始指针,因为子节点的生命周期不应该比其父节点更长。因此,不存在子节点解除引用悬空的父指针的风险。
- Chris Drew