当一个对象保证比其包含的对象长寿时,应该如何存储该对象?
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
class 的 call
方法,但这是不可能的在我正在说明的场景中:可以访问 Context
对象的客户端代码可能无法访问 TopLevelClass
对象。
虽然上面说明的代码功能符合我的需要,但我觉得好像存在代码味道。即,将 TopLevelClass
对象的句柄存储为原始指针并不能传达 Context
class 不负责管理此指针的生命周期这一事实(因为,在这种情况下,TopLevelClass
保证比任何 Context
对象都长)。此外,在使用 C++11 时,我对使用原始指针而不是智能指针犹豫不决(根据 Scott Meyer 在 Effective Modern C++ 中的建议)。
我探索的一种替代方法是使用共享指针将句柄传递给 TopLevelClass
,并将该句柄作为共享指针存储在 Context
class 中。这要求 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
a priori,否则将抛出 std::bad_weak_ptr
异常(有关详细信息,请参阅 this post)。因为在我的例子中,代码中没有创建 std::shared_ptr<TopLevelClass>
,所以我不能使用 std::enable_shared_from_this<T>
方法:我只能使用 [=32= 返回 TopLevelClass
的单个实例] 原始指针,按照我的项目要求,如下
static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
是否存在一种方法可以传达这样一个事实,即 Context
不负责管理其对 TopLevelClass
实例的句柄,因为 TopLevelClass
将保证比 任何 Context
对象?我也乐于接受有关更改设计的建议,只要设计更改不会使上述设计的简单性过于复杂(即创建许多不同的 classes 以便绕过简单地将单个指针传递给 Context
).
的构造函数
感谢您的帮助。
一种选择是使用类型定义来传达非所有权:
#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
的实际 class(类似于 shared_ptr
),它可以更好地隐藏原始指针,但在这种情况下似乎有些矫枉过正。
按照您的方式传递原始指针绝对应该意味着没有所有权被转移。
如果你听到有人说 "don't use raw pointers" 你可能错过了这句话的一部分 - 它应该是 "don't use owning raw pointers",即不应该有你需要的原始指针的地方调用删除。除了可能在一些低级代码中。如果您知道被指向的对象比获取指针的对象长寿,那么只传递指针绝对没有错。
你说的是 "Namely, storing a handle to the TopLevelClass object as a raw pointer does not convey the fact that the Context class is not responsible for managing the lifetime of this pointer"。 相反,存储原始指针正是这个意思 - "This object does not manage the lifetime of the object pointed to by this pointer"。在 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 的视频,但我有点懒得 google 上传合适的视频。
在处理一个项目时,我遇到了一个有趣的问题,当通过其构造函数将一个对象传递给另一个对象时,传入的对象保证比接收对象长寿(就内存生存期而言)。请记住,我仍在学习 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
class 的 call
方法,但这是不可能的在我正在说明的场景中:可以访问 Context
对象的客户端代码可能无法访问 TopLevelClass
对象。
虽然上面说明的代码功能符合我的需要,但我觉得好像存在代码味道。即,将 TopLevelClass
对象的句柄存储为原始指针并不能传达 Context
class 不负责管理此指针的生命周期这一事实(因为,在这种情况下,TopLevelClass
保证比任何 Context
对象都长)。此外,在使用 C++11 时,我对使用原始指针而不是智能指针犹豫不决(根据 Scott Meyer 在 Effective Modern C++ 中的建议)。
我探索的一种替代方法是使用共享指针将句柄传递给 TopLevelClass
,并将该句柄作为共享指针存储在 Context
class 中。这要求 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
a priori,否则将抛出 std::bad_weak_ptr
异常(有关详细信息,请参阅 this post)。因为在我的例子中,代码中没有创建 std::shared_ptr<TopLevelClass>
,所以我不能使用 std::enable_shared_from_this<T>
方法:我只能使用 [=32= 返回 TopLevelClass
的单个实例] 原始指针,按照我的项目要求,如下
static TopLevelClass* getTopLevelClass () {
return new TopLevelClass();
}
是否存在一种方法可以传达这样一个事实,即 Context
不负责管理其对 TopLevelClass
实例的句柄,因为 TopLevelClass
将保证比 任何 Context
对象?我也乐于接受有关更改设计的建议,只要设计更改不会使上述设计的简单性过于复杂(即创建许多不同的 classes 以便绕过简单地将单个指针传递给 Context
).
感谢您的帮助。
一种选择是使用类型定义来传达非所有权:
#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
的实际 class(类似于 shared_ptr
),它可以更好地隐藏原始指针,但在这种情况下似乎有些矫枉过正。
按照您的方式传递原始指针绝对应该意味着没有所有权被转移。
如果你听到有人说 "don't use raw pointers" 你可能错过了这句话的一部分 - 它应该是 "don't use owning raw pointers",即不应该有你需要的原始指针的地方调用删除。除了可能在一些低级代码中。如果您知道被指向的对象比获取指针的对象长寿,那么只传递指针绝对没有错。
你说的是 "Namely, storing a handle to the TopLevelClass object as a raw pointer does not convey the fact that the Context class is not responsible for managing the lifetime of this pointer"。 相反,存储原始指针正是这个意思 - "This object does not manage the lifetime of the object pointed to by this pointer"。在 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 的视频,但我有点懒得 google 上传合适的视频。