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 - 所有权和吸气剂的主要内容,如果未能解决你的问题,请参考以下文章

了解 Nuxt.js 中的状态和吸气剂:吸气剂不起作用

Protobuf-net:如何从隐式所有公共字段的波尔图合同中排除只读属性(只有吸气剂)?

吸气剂'loadingStatus'在空颤时被调用

没有为“Firestore”类型定义吸气剂“实例”

Vuex:为啥我们用大写字母编写突变、动作和吸气剂?

如何使用带有可空值的吸气剂?