在处理项目时,当传入的对象通过其构造函数传递给另一个对象时,我遇到了一个有趣的问题,即传入的对象(在内存生存期方面)保证比对象对象更长。请记住,我仍在学习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
作为参数传递到call
类的Context
方法,但在我说明的方案中这是不可能的:可以访问Context
对象的客户端代码可能无法访问TopLevelClass
对象。
虽然上面说明的代码功能正确满足我的需求,但我觉得好像存在代码味道。也就是说,将句柄存储为TopLevelClass
对象作为原始指针并不表示Context
类不负责管理此指针的生命周期(因为在这种情况下,TopLevelClass
1}}保证比任何Context
对象寿命更长。另外,使用C ++ 11时,我对使用原始指针而不是智能指针犹豫不决(根据Scott Meyer在 Effective Modern C ++ 中的建议)。
我探索的一个替代方法是使用共享指针将句柄传递给TopLevelClass
,并将此句柄作为共享指针存储在Context
类中。这要求TopLevelClass
以下列方式从std::enabled_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;
};
此方法的缺点是除非std::shared_ptr
先验存在TopLevelClass
,否则将抛出std::bad_weak_ptr
异常(有关详细信息,见this post)。因为在我的情况下,代码中没有创建std::shared_ptr<TopLevelClass>
,所以我无法使用std::enable_shared_from_this<T>
方法:我只能使用{{1}返回TopLevelClass
的单个实例根据我的项目要求,原始指针如下
static
是否存在一种方法,表明static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
不负责管理Context
实例的句柄,因为TopLevelClass
将保证比更长久任何 TopLevelClass
对象?我也乐于接受改变设计的建议,只要设计变更不会使上述设计的简单性过于复杂化(即创建许多不同的类以便简单地通过单指针到Context
)的构造函数。
感谢您的帮助。
答案 0 :(得分:3)
传入原始指针的方式绝对应该意味着没有所有权转移。
如果你听过somone说“不要使用原始指针”你可能错过了部分句子 - 它应该是“不要使用拥有原始指针”,即不应该在某个地方,你有一个原始指针,你需要调用删除。除了可能在某些低级代码中。如果您知道被指向的对象比获取指针的对象更长,那么传入指针绝对没有错。
您正在说“即,将TopLevelClass对象的句柄存储为原始指针并不表示Context类不负责管理此指针的生命周期”。 相反,存储原始指针意味着这一点 - “此对象不管理此指针所指向的对象的生命周期”。在C ++ 98样式代码中,它并不一定意味着。
使用指针的另一种方法是使用引用。但是有一些警告,因为你必须在构造函数中初始化它,例如它不能像指针一样设置为nullptr(这也是一件好事)。即:
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);
}
private:
TopLevelClass &_tlc;
};
以下是有关该主题的一些文章:
C ++核心指南:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rr-ptr
Herb Sutter的一些早期文章:
http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/
http://herbsutter.com/2013/05/30/gotw-90-solution-factories/
http://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
http://herbsutter.com/elements-of-modern-c-style/
可能还有很多来自CppCon以及Cpp和Beyond的视频,但我有点懒得去谷歌适当的。
答案 1 :(得分:1)
一种选择是使用类型定义来传达非所有权:
#include <memory>
template<typename T>
using borrowed_ptr = T *;
class TopLevelClass;
class Context {
public:
Context(borrowed_ptr<TopLevelClass> tlc)
: _tlc(std::move(tlc))
{ }
private:
borrowed_ptr<TopLevelClass> _tlc;
};
class TopLevelClass {
public:
std::unique_ptr<Context> getContext() {
return std::make_unique<Context>(this);
}
};
尽管_tlc
仍然可以直接转换为原始指针,但它干净地表达了意图。我们可以创建一个名为borrowed_ptr
的实际类(类似于shared_ptr
),它可以更好地隐藏原始指针,但在这种情况下似乎有些过分。