当一个对象被保证比其包含的对象更长寿时,应该如何存储它
How should an object be stored when it is guaranteed to outlive its containing object?
在处理一个项目时,当通过一个对象的构造函数将一个对象传递到另一个对象时,我遇到了一个有趣的问题,因为传入的对象保证比接收方对象寿命长(就内存寿命而言)。请记住,我仍在学习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::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;
};
这种方法的缺点是,除非TopLevelClass
先验存在std::shared_ptr
,否则将抛出std::bad_weak_ptr
异常(有关更多信息,请参阅本文)。由于在我的情况下,代码中没有创建std::shared_ptr<TopLevelClass>
,因此我不能使用std::enable_shared_from_this<T>
方法:根据我的项目要求,我只能使用static
原始指针返回TopLevelClass
的单个实例,如下
static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
有没有一种方法可以传达这样一个事实,即Context
不负责管理其对TopLevelClass
实例的句柄,因为TopLevelClass
将保证比任何Context
对象都要长?我也愿意接受关于更改设计的建议,只要设计更改不会使上述设计的简单性过于复杂(即,创建许多不同的类,以避免简单地将单个指针传递到Context
的构造函数中),就可以完全绕过问题。
谢谢你的帮助。
以绝对的方式传递原始指针应该意味着没有所有权被转移。
如果你听到有人说"不要使用原始指针",你可能错过了句子的一部分——应该是"不要使用拥有原始指针的",也就是说,不应该有一个地方有你需要调用delete的原始指针。可能在一些低级别代码中除外。如果你知道被指向的对象比获得指针的对象更长寿,那么只传递指针绝对没有错。
您说的是"也就是说,将TopLevelClass对象的句柄存储为原始指针并不能传达Context类不负责管理该指针的生存期这一事实"恰恰相反,存储一个原始指针意味着——"此对象不管理此指针指向的对象的生存期"。在C++98风格的代码中,这并不一定意味着这一点。
使用指针的另一种选择是使用引用。不过,有一些注意事项,例如,您必须在构造函数中对其进行初始化,并且不能像指针一样将其设置为nullptr(这也是一件好事)。I.e:
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的视频,但我有点懒得在谷歌上搜索合适的视频。
一种选择是使用类型定义来传达非所有权:
#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
)的实际类,它可以更好地隐藏原始指针,但在这种情况下,这似乎有些过头了。
- 将字符串存储在c++中的稳定内存中
- std::原子加载和存储都需要吗
- C++:将控制台输出存储在宏中更好吗
- 使用QProcess执行命令,并将结果存储在QStringList中
- 访问存储在向量C++中的结构的多态成员
- 如何从存储在std::映射中的std::集中删除元素
- 存储模板类型以强制转换回派生<T>
- 类型总是使用其大小存储在内存中吗
- 当字符串存储在变量中时,如何将字符串转换为wchar_t
- 使用无符号字符数组有效存储内存
- 如何在cpp.中使用协议缓冲区存储大缓冲区/数组(char/int)
- 使用 pqxx 将 std::vector 存储在 postgresql 中,并从数据库中检索它
- 带结构的二维矢量:如何存储元素
- 添加存储在向量中的大整数的函数出现问题
- 从文件中读取多个字节,并将它们存储在C++中进行比较
- 在std::vector上存储带有模板的类实例
- 谷歌测试中的期望值存储在哪里
- 为什么C中的通用链表中存储的数据已损坏
- 在c++中获取两个大int,并将它们存储在数组中
- 在reactor中存储eventHandlers的最佳方式是什么