c++11 - 所有权和吸气剂
Posted
技术标签:
【中文标题】c++11 - 所有权和吸气剂【英文标题】:c++11 - Ownership and getters 【发布时间】:2013-06-07 21:08:54 【问题描述】:我是 C++ 的新手,我很难理解所有权,特别是使用 getter。下面是一些示例代码:
class GameObject
public:
Transform *transform();
private:
Transform _transform;
;
我猜原始指针使用起来是不安全的,因为稍后当对象不再存在时有人可以访问它?
所以我考虑使用 unique_ptr 作为变换成员,因为 GameObject 是唯一拥有变换的对象。但是我不能从吸气剂那里返回它,可以吗?但是话又说回来,为什么我会首先使用 unique_ptr 而不是像上面那样将其添加为成员?
那么为什么不使用 shared_ptr 呢?这对我来说似乎是错误的,我不想分享所有权,GameObject 是所有者,其他人可以访问它......
那是什么?参考?我猜 shared_ptr 似乎是最明智的选择,因为其他人可以安全地保留对 transform 的引用,但是如果封闭的 GameObject 被破坏,使变换无用又有什么好处呢?我可能只是在这里以错误的方式思考所有权,但在我看来,每一种方式都是错误的。感谢您的帮助。
【问题讨论】:
您可以通过 shared_ptr 存储 _transform 成员,并从您的 getter 函数返回weak_ptr。 【参考方案1】:所有权和原始指针?
智能指针通常带有所有权语义,因此当您不想提供/共享所有权时使用它们中的任何一个都是一个坏主意。
在我自己的代码中,我决定了以下约定:
原始指针意味着没有所有权转移/共享
这意味着如果某些函数接收原始指针作为参数,则它没有所有权(因此,不应尝试删除它)。同样,返回原始指针的函数不会转移/共享所有权(因此,调用者也不应该尝试删除它)。
所以在你的情况下,你可以返回一个指针而不是智能指针。
指针还是引用?
这意味着指针和引用之间的选择仅取决于以下规则解决的一个因素:
如果nullptr
/NULL
值有意义,则使用指针。
如果nullptr
/NULL
值没有意义和/或不受欢迎,则使用引用。
常量和成员变量?
我看到了一些关于 constness 的答案,所以我添加了我自己的两分钱:
将非常量值返回给内部变量是一种设计选择。您必须做的是在以下 getter 之间进行选择:
const Transform * getTransform() const ;
Transform * getTransform() ;
我将上面的访问器(滥用)称为“属性”,非 const 版本使用户能够在闲暇时修改内部 Transform 对象,而无需先询问您的班级。您会注意到,如果 GameObject 是 const,则无法修改其内部 Transform 对象(唯一可访问的 getter 是 const 的,返回 const)。您还会注意到,您无法更改 GameObject 中的 Transform 对象的地址。您只能通过该地址更改数据指针(这与使成员变量公开不同)
const Transform *& getTransform() const ;
Transform *& getTransform() ;
这与上面的情况类似,但是由于引用,用户可以直接访问指针地址,这意味着非常量访问器或多或少类似于将成员变量公开。
const Transform * getTransform() const ;
void setTransform(Transform * p_transform) ;
我(粗暴地)将上面的访问器称为“getter/setter”:如果用户想要修改转换值,那么他必须使用 setTransform。您对正在做的事情有更多的控制权(请注意,在我的代码中,您传递一个 unique_ptr 或 auto_ptr 的 setter 来表示获得 p_transform 对象的总所有权。您会注意到,如果 GameObject 是 const ,无法修改其内部的 Transform 对象(setter 是非常量的)。
Transform * getTransform() const ;
我(滥用)将访问器称为“下标”,因为在指针数组中,数组可以是 const,因此内部地址是 const,但该地址指向的数据是非常量,所以它可以修改。这通常意味着 Transform 对象并不真正由您的 GameObject 对象“拥有”。事实上,为了便于使用,GameObject 很可能只引用了一些外部对象。
【讨论】:
【参考方案2】:对于阅读您的类定义的任何人来说,很明显(或应该很明显)GameObject
拥有一个transform
。你是对的,“共享所有权”不是暗示或期望的。由于不可能永远无法从GameObject
获得transform
,因此您不需要像指针(原始或其他)那样表达可能的空值的东西,因此返回引用(可能)const
似乎是最惯用的做法。
您说原始指针不安全,但它并不比任何其他直接访问方法更不安全。您提供对拥有的转换对象(而不是其副本)的访问的任何方式都使客户端有机会获取其地址并将其存储在拥有GameObject
的生命周期之外。不做愚蠢的事情真的取决于客户。没有办法绝对防止这种情况发生,所以你应该让你的界面简单明了,不易误用。
【讨论】:
+1 用于设计解决方案以防止错误地做事。这是 boost 库的核心理念。 大家的回答都很好,非常感谢! @Balog Pal:那不会有副本吗? @cboe:是的,这就是重点。你避免“泄露胆量”。除非得到的成员是集合副本,否则在大多数情况下几乎不会被注意到。 @BalogPal:是的,按值返回当然可以避免任何生命周期问题的可能性;我做了一个隐含的假设,即转换是实体多于价值,但这不一定是正确的假设。 绝对是实体多于价值,不过,我什至没有考虑过 :) Thx【参考方案3】:其他人已经回答了有关所有权的问题,但我仍然想再补充一点。
放弃非const
指针或对private
成员的引用通常是一个非常糟糕的主意:它基本上等同于使该私有成员public
。 在其他换句话说,如果您编写一个返回非常量引用或指向非常量成员的指针的 getter,那么您的代码基本上等同于以下内容:
class GameObject
public:
Transform _transform; // and you don't have to write a getter
;
但这是个坏主意。
如果您真的必须编写一个 getter,请按照 Balog Pal 的建议为私有成员提供一个 const 引用。如果您需要非常量引用,那么您应该重新考虑您的设计。
【讨论】:
听起来不错,但将其设为只读不是一种有效的方法吗? @cboe 是的,绝对可以。【参考方案4】:经验法则:只有在绝对无法避免的情况下,您才需要考虑指针和所有权。 C++ 具有良好的值语义(用 const ref 扩展),因此您可以在需要指针之前获得相当长的时间,无论是智能的还是愚蠢的。
在您的示例中,_transform 作为直接成员非常好。 如果你想要一个吸气剂,你可以做它
Transform transform() const return _transform;
在某些特殊情况下可能是
const Transform& transform() const return _transform;
带有生命周期的文档。只有在合理的情况下才这样做。
嗯,显然最好的方法是完全不使用 getter。面向对象是关于行为的,让你的类做你的工作,而不是把它用作你不断从外部戳和查询的数据转储。
【讨论】:
"当需要转让所有权时,则需要一个指针。"这听起来比您的经验法则更容易理解。 我看不出矛盾。当指针使用不可避免时,有很好的线程列出案例,您的案例在列表中。【参考方案5】:我认为重要的是您的设计模拟了Transform
对象由其GameObject
拥有的事实。
如果不打算共享所有权(换句话说,如果 Transform
对象的生命周期严格受拥有它的唯一 GameObject
的生命周期约束),那么我会避免 @987654325 @。
在我看来,你的第一选择应该是你已经实际选择的那个:只要有一个Transform
类型的子对象。但是,返回对它的引用而不是原始指针:
class GameObject
public:
Transform& transform();
private:
Transform _transform;
;
这是传达您提供对_transform
子对象的访问权限但拥有所有权这一事实的最清晰的可能方式。
客户不必担心诸如“我应该何时以及如何删除这个对象?我应该完全删除它吗?”之类的问题,因为你的界面很清楚:没有所有权,不负责任。
另一方面,有一些原因可能会阻止您这样做。一个可能的原因是GameObject
可能拥有也可能不拥有Transform
对象。如果是这种情况,您可能希望改用 unique_ptr
(并返回一个原始指针,它可能为空)。
使用unique_ptr
的另一个原因可能是Transform
子对象的构造需要延迟,因为构造函数接受了一些需要计算的值,而无法在构造函数的初始化列表中提供。
一般来说,除非您有充分的理由这样做,否则请选择最简单的解决方案,并仅嵌入 Transform
类型的子对象,从而提供参考。
【讨论】:
你能解释一下最后一段吗?如果使用唯一指针,返回类型是否应该是一个将被“移动”到客户端的 unique_ptr? @Koushik:如果Transform
的构造函数必须使用需要一些非平凡计算的参数调用,并且该计算不能在GameObject
的构造函数的初始化列表中执行,或者最好放在构造函数体中,这样Transform
子对象就无法初始化。当然,如果Transform
有默认构造函数,那么我们可以让_transform
子对象默认构造,然后在主体中重新分配或修改它。
@Koushik:甚至,Transform
对象可以由客户端创建并通过unique_ptr
传递给GameObject
。在任何情况下,GameObject
都不会通过返回unique_ptr
来提供对拥有的_transform
指针的访问。返回unique_ptr
将意味着转移所有权,这在此处是不需要的。 GameObject
将返回一个引用或一个原始指针(取决于是否总是有一个 Transform
对象被 GameObject
嵌入)
我理解那部分,但我不明白如何使用 unique_ptr 来推迟转换的构造。
@AndyProwl 啊现在我明白了。谢谢:)以上是关于c++11 - 所有权和吸气剂的主要内容,如果未能解决你的问题,请参考以下文章