我是C ++的新手,我在围绕所有权问题上遇到了麻烦,特别是在吸气时。这是一些示例代码:
class GameObject {
public:
Transform *transform();
private:
Transform _transform;
};
我猜一个原始指针是不安全的,因为有人可以在以后不再存在该对象时访问它?
所以我考虑使用unique_ptr作为转换成员,因为GameObject是唯一拥有转换的人。但是我不能从吸气器那里回来,是吗?但话又说回来,为什么我会首先使用unique_ptr而不是像上面那样添加它?
那么为什么不使用shared_ptr?这对我来说似乎不对,我不想分享所有权,GameObject是所有者,其他人可能会访问它...
那是什么?参考?我想shared_ptr似乎是最明智的选择,因为其他人可以安全地保留对变换的引用,但是如果封闭的GameObject被破坏,那么变换是无用的呢?我可能只是想在这里以错误的方式考虑所有权,但每一种方式对我来说都是错误的。谢谢你的帮助。
答案 0 :(得分:18)
对于阅读课程定义的任何人GameObject
拥有transform
,显然(或应该是显而易见的)。你是对的,“共享所有权”并不是暗示或期望的。由于不可能无法从transform
获得GameObject
,因此您不需要像指针(原始或其他)那样表达可能的无效的东西,因此返回引用(可能)const
似乎是最惯用的事情。
你说原始指针是不安全的,但它并不比任何其他直接访问方法更不安全。以任何方式提供对拥有的转换对象(而不是它的副本)的访问权限,使客户端有机会获取其地址并将其存储在拥有的GameObject
的生命周期之外。客户不应该做蠢事。绝对没有办法防止这种情况,所以你应该让你的界面简单明了,很难无意中误用。
答案 1 :(得分:15)
我认为重要的是你的设计模拟Transform
对象归其GameObject
所有的事实。
如果不打算共享所有权(换句话说,如果Transform
对象的生命周期严格受限于它的生命周期{且仅GameObject
拥有它),那么我会避免shared_ptr
。
在我看来,你的第一个选择应该是你实际选择的那个:只有一个Transform
类型的子对象。但是,返回对它的引用而不是原始指针:
class GameObject {
public:
Transform& transform();
private:
Transform _transform;
};
这是传达这样一个事实的最清晰的方式:您正在提供对_transform
子对象的 所有权的访问权限。
客户不必担心诸如“何时以及如何删除此对象?我应该删除它吗?”之类的问题,因为您的界面清楚了解它:没有所有权,没有责任。
另一方面,有理由可能会阻止您这样做。一个可能的原因可能是GameObject
可能拥有或不拥有Transform
个对象。如果是这种情况,您可能希望使用unique_ptr
代替(并返回原始指针,该指针可能为null)。
使用unique_ptr
的另一个原因可能是Transform
子对象的构造需要延迟,因为构造函数接受一些需要计算的值,并且无法在构造函数中提供初始化列表。
一般情况下,除非你有充分的理由这样做,否则最喜欢最简单的解决方案,只需嵌入Transform
类型的子对象,即可提供参考。
答案 2 :(得分:5)
在您的示例中,_transform作为直接成员非常精细。 如果你想要一个吸气剂,你可以做到它
Transform transform() const {return _transform; }
在某些特殊情况下,它可能是
const Transform& transform() const {return _transform; }
有生命周期的文件。只有在合理的情况下才这样做。
嗯,显然,吸气剂的最佳方法是根本没有它们。面向对象是关于行为,让你的类做你的工作,而不是使用它作为你从外面戳戳和查询的数据转储。
答案 3 :(得分:3)
智能指针通常带有所有权语义,所以当你不想给予/分享所有权时使用它们是个坏主意。
在我自己的代码中,我决定了以下约定:
原始指针表示没有所有权转移/共享
这意味着如果某些函数接收到一个原始指针作为参数,那么它就没有所有权(因此,不应该尝试删除它)。同样,返回原始指针的函数不会传输/共享所有权(因此,调用者也不应该尝试删除它)。
因此,在您的情况下,您可以返回指针而不是智能指针。
这意味着指针和引用之间的选择仅取决于以下规则解决的一个因素:
nullptr
/ NULL
值有意义,则使用指针。nullptr
/ NULL
值没有意义和/或不合适,请使用引用。我在constness上看到了一些答案,所以我加上自己的两分钱:
将非const值返回给内部变量是一种设计选择。你必须做的是在以下吸气剂之间做出选择:
const Transform * getTransform() const ;
Transform * getTransform() ;
我在“属性”上面(滥用)调用访问器,非const版本允许用户在他/她的闲暇时修改内部的Transform对象,而无需先询问您的课程。你会注意到,如果GameObject是const,则无法修改其内部Transform对象(唯一可访问的getter是const one,返回const)。您还会注意到,您无法在GameObject中更改Transform对象的地址。您只能通过该地址更改数据指针(这与使成员变量公开不同)
const Transform *& getTransform() const ;
Transform *& getTransform() ;
这与上面的情况类似,但是由于引用,用户可以直接访问指针地址,这意味着非const访问器或多或少类似于使成员变量公开。
const Transform * getTransform() const ;
void setTransform(Transform * p_transform) ;
我在“getter / setter”上面(滥用)调用访问器:如果用户想要修改变换值,那么他必须使用setTransform。你可以对你正在做的事情有更多的控制(请注意,在我的代码中,你通过unique_ptr或者auto_ptr来表示获取p_transform对象的总拥有权。你会注意到,如果GameObject是const的话,没有办法修改其内部的Transform对象(setter是非const)。
Transform * getTransform() const ;
我在“下标”上面(滥用)调用访问器,就像在指针数组中一样,数组可以是const,因此内部地址是const,但是这个地址指向的数据是非const的,所以它可以修改。这通常意味着您的GameObject对象并不真正“拥有”Transform对象。实际上,为了便于使用,GameObject很可能只引用了一些外部对象。
答案 4 :(得分:2)
其他人已经回答了有关所有权的问题,但我还想补充一点。
放弃非const
指针或对private
成员的引用通常是一个非常糟糕的主意:它基本上等同于制作私有成员public
。 换句话说,如果你编写一个getter返回一个非const引用或一个指向非const成员的指针,那么你的代码基本上等同于以下内容:
class GameObject {
public:
Transform _transform; // and you don't have to write a getter
};
但这是一个坏主意。
如果你真的需要编写一个getter,请按照Balog Pal的建议给私人成员提供const引用。如果您需要非const引用,那么您应该重新考虑您的设计。