13

我是 C++ 的新手,我很难理解所有权,特别是使用吸气剂。这是一些示例代码:

class GameObject {
public:
  Transform *transform();
private:
  Transform _transform;
};

我猜原始指针使用起来是不安全的,因为当对象不再存在时有人可以稍后访问它?

  1. 所以我考虑为变换成员使用 unique_ptr,因为 GameObject 是唯一拥有变换的成员。但是我不能从吸气剂那里返回它,可以吗?但是话又说回来,为什么我会首先使用 unique_ptr 而不是像上面那样将其添加为成员?

  2. 那么为什么不使用 shared_ptr 呢?这对我来说似乎是错误的,我不想分享所有权,GameObject 是所有者,其他人可以访问它......

  3. 那是什么?参考?我想 shared_ptr 似乎是最明智的选择,因为其他人可以安全地保留对 transform 的引用,但是如果封闭的 GameObject 被破坏,使变换无用又有什么好处呢?我可能只是在这里以错误的方式思考所有权,但在我看来,每一种方式都是错误的。谢谢你的帮助。

4

5 回答 5

18

对于任何阅读您的类定义并GameObject拥有transform. 你是对的,“共享所有权”不是暗示或期望的。由于不可能永远无法transform从 a 获得 aGameObject您不需要像指针(原始或其他)这样表达可能的空值的东西,因此返回引用(可能)const似乎是最惯用的事情。

您说原始指针不安全,但它并不比任何其他直接访问方法更不安全。您提供对拥有的转换对象(而不是它的副本)的访问的任何方式都使客户端有机会获取其地址并将其存储在拥有的生命周期之外GameObject。不做愚蠢的事情真的取决于客户。没有办法绝对防止这种情况发生,因此您应该使您的界面简单明了,并且难以不经意地错误使用。

于 2013-06-11T21:19:29.420 回答
15

Transform我认为重要的是您的设计模拟了对象由其拥有的事实GameObject

如果所有权不是要共享的(换句话说,如果对象的生命周期Transform严格受GameObject拥有它的对象的生命周期约束),那么我会避免shared_ptr.

在我看来,你的第一选择应该是你实际选择的那个:只要有一个 type 的子对象Transform。但是,返回对它的引用而不是原始指针:

class GameObject {
public:
    Transform& transform();
private:
    Transform _transform;
};

这是传达您正在提供对子对象的访问权但未授予_transform子对象所有权这一事实的最清晰的可能方式。

客户不必担心诸如“我应该何时以及如何删除这个对象?我应该完全删除它吗? ”之类的问题,因为你的界面很清楚:没有所有权,没有责任。

另一方面,有一些原因可能会阻止您这样做。一个可能的原因可能是 aGameObject可能拥有也可能不拥有一个Transform对象。如果是这种情况,您可能想要使用 aunique_ptr代替(并返回一个原始指针,它可能为空)。

使用 a 的另一个原因unique_ptr可能是Transform子对象的构造需要延迟,因为构造函数接受了一些需要计算的值,而无法在构造函数的初始化列表中提供。

一般来说,除非你有充分的理由这样做,否则更喜欢最简单的解决方案,只嵌入一个 type 的子对象Transform,放弃引用。

于 2013-06-11T21:21:55.810 回答
5

经验法则:只有在绝对无法避免的情况下,您才会为指针和所有权而烦恼。C++ 具有良好的值语义(使用 const ref 扩展),因此您可以在需要指针之前获得相当长的时间,无论是智能的还是愚蠢的。

在您的示例中,_transform 作为直接成员非常好。如果你想要一个吸气剂,你可以做到

Transform transform() const {return _transform; }

在某些特殊情况下,它可能是

const Transform& transform() const {return _transform; }

与终身文件。只有在有正当理由的情况下才这样做。

好吧,显然,最好的方法是根本不拥有它们。面向对象是关于行为的,让你的类做你的工作,而不是把它用作你不断从外部戳和查询的数据转储。

于 2013-06-11T21:22:44.500 回答
3

所有权和原始指针?

智能指针通常带有所有权语义,因此当您不想提供/共享所有权时使用它们中的任何一个都是一个坏主意。

在我自己的代码中,我决定了以下约定:

原始指针意味着没有所有权转移/共享

这意味着如果某些函数接收原始指针作为参数,则它没有所有权(因此,不应尝试删除它)。同样,返回原始指针的函数不会转移/共享所有权(因此,调用者也不应该尝试删除它)。

因此,在您的情况下,您可以返回指针而不是智能指针。

指针还是参考?

这意味着指针和引用之间的选择仅取决于由以下规则解决的一个因素:

  • 如果nullptr/NULL值有意义,则使用指针。
  • 如果nullptr/NULL值没有意义和/或不受欢迎,则使用引用。

常量和成员变量?

我看到了一些关于 constness 的答案,所以我添加了我自己的两分钱:

将非常量值返回给内部变量是一种设计选择。您必须做的是在以下吸气剂之间进行选择:

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 来表达对 p_transform 对象的总所有权的获取。你会注意到,如果 GameObject 是 const ,无法修改其内部 Transform 对象(setter 是非常量)。

Transform * getTransform() const ;

我(粗暴地)将访问器称为“下标”,因为在指针数组中,数组可以是 const,因此内部地址是 const,但是该地址指向的数据是非常量的,因此可以对其进行修改. 这通常意味着 Transform 对象并不真正由您的 GameObject 对象“拥有”。事实上,为了便于使用,GameObject 很可能只引用了一些外部对象。

于 2013-06-12T11:08:47.573 回答
2

其他人已经回答了有关所有权的问题,但我仍然想再补充一点。

放弃const对成员的非指针或引用private通常是一个非常糟糕的主意:它基本上等同于制作该私有成员public换句话说,如果您编写一个返回非常量引用或指向非常量成员的指针的 getter,那么您的代码基本上等同于以下内容:

class GameObject {
public:
  Transform _transform; // and you don't have to write a getter
};

但这是个坏主意。

如果您真的必须编写一个 getter,请按照 Balog Pal 的建议为私有成员提供一个 const 引用。如果您需要非常量参考,那么您应该重新考虑您的设计。

于 2013-06-11T21:59:03.867 回答