在堆上创建对象并在程序的其余部分通过指针容器访问
Creating objects on the heap and accessing via containers of pointers in rest of program
我一直无法从成员变量的角度理解共享指针的工作原理。 SO 上有很多解释共享指针和 how/when 来使用它们的好问题(this, this, and this,等等)。然而,当我们处理 class 实例变量时,我一直在努力理解它们是如何工作的,尤其是那些容器(如 std::vector
)。
我的工作流程涉及
- 正在从文件中读取数据
- 将该数据存储在 class
的容器中
- 在包含 class
的实例中访问、修改和传递该数据
我想了解我的代码是否(以及在哪里)进行了不必要的复制,或者我传递的常量引用是否足够好。读了一些书后,我倾向于认为我应该在堆上创建一次对象并传递共享指针而不是常量引用,但我说不出为什么。
我在下面(大致)粘贴了我程序的部分内容(大致)它们当前的实现。我的问题是:
- 对于
PointCloud
class,我是否应该将指向 Point3D
对象的共享指针存储在 _data
向量中?这样,在过滤操作期间,我可以通过使用指向适当 Point3D 对象的共享指针填充它们各自的 _data
成员变量来填充我的 _inliers
和 _outliers
PointCloud 变量?
- 对于
Scene
class和Filter
class,让PointCloud
类型的成员变量共享指向[=15的指针是不是更好? =]?我很确定我当前的 getter 实现 return 副本,并且这些 PointCloud
对象可以包含数百万个 Point3D
对象,所以它们并不小。这会比 return 一个常量引用更好吗(就像我在 PointCloud::getData()
? 中所做的那样)
最终我认为我的问题的根源归结为:我是否应该在堆上创建几乎所有的 Point3D 对象并仅将共享指针存储在我的容器中 class 成员变量?本质上,这将使用一种 "shared memory" 模型。如果是这样,效率会提高多少?如果不是,为什么不呢?
这是我程序的一些组件。
(注意:实际上并不是所有的实现都是内联的。为了简洁起见,我只是在这里这样做了)。
scene.h
#include "pointcloud.h"
#include "point3D.h" // just defines the Point3D class
class Scene
{
private:
// Other member variables...
/** Pointcloud */
PointCloud _pointcloud;
public:
// ...Public functions...
inline PointCloud getPointCloud() const; // Return point cloud
inline void readPoints3D(const std::string &path_to_file);
};
PointCloud Scene::getPointCloud() const { return _pointcloud; }
void Scene::readPoints3D(const std::string &path_to_file)
{
std::ifstream file(path_to_file.c_str());
// Run through each line of the text file
std::string line;
std::string item;
while (std::getline(file, line))
{
// Initialize variables id, x, y, z, r, g, b from file data
Point3D pt(id, x, y, z, r, g, b); // Create Point3D object
_pointcloud.addPoint(pt); // Add point to pointcloud
}
file.close();
}
pointcloud.h
#include "point3D.h"
#include <vector>
class PointCloud
{
private:
/** PointCloud data */
std::vector<Point3D> _data;
public:
// Public member functions
inline std::vector<Point3D> & getData();
inline const std::vector<Point3D> & getData() const;
inline void addPoint(const Point3D &pt);
};
const std::vector<Point3D> & PointCloud::getData() const { return _data; }
std::vector<Point3D> & PointCloud::getData() { return _data; }
void PointCloud::addPoint(const Reprojection::Point3D &pt)
{
_data.push_back(pt); // Add the point to the data vector
}
filter.h
#include "pointcloud.h"
// Removes 3D points from a pointcloud if they don't meet certain conditions
class Filter
{
private:
// Good points that should stay in the pointcloud
PointCloud _inliers;
// Removed points
PointCloud _outliers;
public:
Filter(const PointCloud &pointcloud) // Constructor
void filterPointCloud(); // Filtering operation
inline PointCloud getInliers();
inline PointCloud getOutliers();
}
Filter::Filter(const PointCloud &pointcloud) :
_inliers(pointcloud), _outliers(PointCloud()) {}
PointCloud getInliers() { return _inliers; }
PointCloud getOutliers() { return _outliers; }
driver.cpp
#include "scene.h"
#include "pointcloud.h"
#include "filter.h"
// Some function that writes my pointcloud to file
void writeToFile(PointCloud &cloud);
int main()
{
Scene scene;
scene.readPoints3D("points3D_file.txt");
PointCloud cloud = scene.getPointCloud();
Filter f(cloud);
f.filterPointCloud();
writeToFile(f.getInliers());
writeToFile(f.getOutlier());
}
这一切都归结为对象的生命周期,以及谁控制它们。 std::shared_ptr
的美妙之处在于它会自动为您处理;当对象的最后一个 shared_ptr
被销毁时,对象本身也随之被销毁。只要对象的 shared_ptr
存在,它就仍然有效。
引用(const 与否)没有这种保证。生成对超出范围的对象的引用非常容易。但是,如果可以通过其他方式保证对象的生命周期会超过引用,那么引用比shared_ptr
.
更灵活高效。
一个可以保证对象生命周期的地方是当你调用一个以对象作为参数的函数时。该对象将一直存在到函数结束,除非该函数试图做一些像 delete
这样疯狂的事情。唯一需要 shared_ptr
作为参数的情况是函数尝试保存指针供以后使用时。
当您遇到一生的问题时,您得到的一条建议是 "use shared_ptr
"。一旦你采纳了这个建议,你就会遇到两个问题。
首先,您现在正在使用shared_ptr
,所以得到它来表达您的生活问题。而且你仍然有你原来的生活问题。
shared_ptr
表达了一个概念,即特定数据位的生命周期应该是该数据位的所有 shared_ptr
生命周期的并集。这是对对象生命周期的复杂描述。
有时您实际上需要复杂的对象生命周期描述。其他时候你不会。
让您的代码尽可能简单,当您不需要复杂的对象生命周期时,不要使用指针。
如果您的代码简单且功能纯粹,则几乎不需要指针。如果你有纠缠状态,那么你可能需要指针,但避免纠缠状态是比使指针和生命周期管理变得极其复杂更好的解决方案。
尽可能使用值;值很简单,易于推理,并且允许缩放。尽可能避免将对象标识(即它的内存位置)用作对您的系统有意义的任何东西。
使用 shared_ptr
的理由很充分,但简单的 "allocate everything on the heap and use shared_ptr
and don't worry about object lifetime" 会导致无法维护的依赖统计信息、泄漏、崩溃、组件之间随机隐藏的依赖项、不可预测的对象生命周期以及由于以下原因导致的糟糕性能缓存不连贯。
全面的垃圾收集系统仍然存在严重而复杂的对象生命周期问题和严重的局部性问题; shared_ptr
不是完整的垃圾收集系统。
我一直无法从成员变量的角度理解共享指针的工作原理。 SO 上有很多解释共享指针和 how/when 来使用它们的好问题(this, this, and this,等等)。然而,当我们处理 class 实例变量时,我一直在努力理解它们是如何工作的,尤其是那些容器(如 std::vector
)。
我的工作流程涉及
- 正在从文件中读取数据
- 将该数据存储在 class 的容器中
- 在包含 class 的实例中访问、修改和传递该数据
我想了解我的代码是否(以及在哪里)进行了不必要的复制,或者我传递的常量引用是否足够好。读了一些书后,我倾向于认为我应该在堆上创建一次对象并传递共享指针而不是常量引用,但我说不出为什么。
我在下面(大致)粘贴了我程序的部分内容(大致)它们当前的实现。我的问题是:
- 对于
PointCloud
class,我是否应该将指向Point3D
对象的共享指针存储在_data
向量中?这样,在过滤操作期间,我可以通过使用指向适当 Point3D 对象的共享指针填充它们各自的_data
成员变量来填充我的_inliers
和_outliers
PointCloud 变量? - 对于
Scene
class和Filter
class,让PointCloud
类型的成员变量共享指向[=15的指针是不是更好? =]?我很确定我当前的 getter 实现 return 副本,并且这些PointCloud
对象可以包含数百万个Point3D
对象,所以它们并不小。这会比 return 一个常量引用更好吗(就像我在PointCloud::getData()
? 中所做的那样)
最终我认为我的问题的根源归结为:我是否应该在堆上创建几乎所有的 Point3D 对象并仅将共享指针存储在我的容器中 class 成员变量?本质上,这将使用一种 "shared memory" 模型。如果是这样,效率会提高多少?如果不是,为什么不呢?
这是我程序的一些组件。
(注意:实际上并不是所有的实现都是内联的。为了简洁起见,我只是在这里这样做了)。
scene.h
#include "pointcloud.h"
#include "point3D.h" // just defines the Point3D class
class Scene
{
private:
// Other member variables...
/** Pointcloud */
PointCloud _pointcloud;
public:
// ...Public functions...
inline PointCloud getPointCloud() const; // Return point cloud
inline void readPoints3D(const std::string &path_to_file);
};
PointCloud Scene::getPointCloud() const { return _pointcloud; }
void Scene::readPoints3D(const std::string &path_to_file)
{
std::ifstream file(path_to_file.c_str());
// Run through each line of the text file
std::string line;
std::string item;
while (std::getline(file, line))
{
// Initialize variables id, x, y, z, r, g, b from file data
Point3D pt(id, x, y, z, r, g, b); // Create Point3D object
_pointcloud.addPoint(pt); // Add point to pointcloud
}
file.close();
}
pointcloud.h
#include "point3D.h"
#include <vector>
class PointCloud
{
private:
/** PointCloud data */
std::vector<Point3D> _data;
public:
// Public member functions
inline std::vector<Point3D> & getData();
inline const std::vector<Point3D> & getData() const;
inline void addPoint(const Point3D &pt);
};
const std::vector<Point3D> & PointCloud::getData() const { return _data; }
std::vector<Point3D> & PointCloud::getData() { return _data; }
void PointCloud::addPoint(const Reprojection::Point3D &pt)
{
_data.push_back(pt); // Add the point to the data vector
}
filter.h
#include "pointcloud.h"
// Removes 3D points from a pointcloud if they don't meet certain conditions
class Filter
{
private:
// Good points that should stay in the pointcloud
PointCloud _inliers;
// Removed points
PointCloud _outliers;
public:
Filter(const PointCloud &pointcloud) // Constructor
void filterPointCloud(); // Filtering operation
inline PointCloud getInliers();
inline PointCloud getOutliers();
}
Filter::Filter(const PointCloud &pointcloud) :
_inliers(pointcloud), _outliers(PointCloud()) {}
PointCloud getInliers() { return _inliers; }
PointCloud getOutliers() { return _outliers; }
driver.cpp
#include "scene.h"
#include "pointcloud.h"
#include "filter.h"
// Some function that writes my pointcloud to file
void writeToFile(PointCloud &cloud);
int main()
{
Scene scene;
scene.readPoints3D("points3D_file.txt");
PointCloud cloud = scene.getPointCloud();
Filter f(cloud);
f.filterPointCloud();
writeToFile(f.getInliers());
writeToFile(f.getOutlier());
}
这一切都归结为对象的生命周期,以及谁控制它们。 std::shared_ptr
的美妙之处在于它会自动为您处理;当对象的最后一个 shared_ptr
被销毁时,对象本身也随之被销毁。只要对象的 shared_ptr
存在,它就仍然有效。
引用(const 与否)没有这种保证。生成对超出范围的对象的引用非常容易。但是,如果可以通过其他方式保证对象的生命周期会超过引用,那么引用比shared_ptr
.
一个可以保证对象生命周期的地方是当你调用一个以对象作为参数的函数时。该对象将一直存在到函数结束,除非该函数试图做一些像 delete
这样疯狂的事情。唯一需要 shared_ptr
作为参数的情况是函数尝试保存指针供以后使用时。
当您遇到一生的问题时,您得到的一条建议是 "use shared_ptr
"。一旦你采纳了这个建议,你就会遇到两个问题。
首先,您现在正在使用shared_ptr
,所以得到它来表达您的生活问题。而且你仍然有你原来的生活问题。
shared_ptr
表达了一个概念,即特定数据位的生命周期应该是该数据位的所有 shared_ptr
生命周期的并集。这是对对象生命周期的复杂描述。
有时您实际上需要复杂的对象生命周期描述。其他时候你不会。
让您的代码尽可能简单,当您不需要复杂的对象生命周期时,不要使用指针。
如果您的代码简单且功能纯粹,则几乎不需要指针。如果你有纠缠状态,那么你可能需要指针,但避免纠缠状态是比使指针和生命周期管理变得极其复杂更好的解决方案。
尽可能使用值;值很简单,易于推理,并且允许缩放。尽可能避免将对象标识(即它的内存位置)用作对您的系统有意义的任何东西。
使用 shared_ptr
的理由很充分,但简单的 "allocate everything on the heap and use shared_ptr
and don't worry about object lifetime" 会导致无法维护的依赖统计信息、泄漏、崩溃、组件之间随机隐藏的依赖项、不可预测的对象生命周期以及由于以下原因导致的糟糕性能缓存不连贯。
全面的垃圾收集系统仍然存在严重而复杂的对象生命周期问题和严重的局部性问题; shared_ptr
不是完整的垃圾收集系统。