【问题标题】:private object pointer vs object value, and returning object internals私有对象指针与对象值,并返回对象内部
【发布时间】:2010-06-26 22:39:49
【问题描述】:

相关:C++ private pointer "leaking"?

根据 Effective C++(第 28 条),“避免将句柄(引用、指针或迭代器)返回到对象内部。它增加了封装性,帮助 const 成员函数充当 const,并最大限度地减少了悬空句柄的创建。”

p>

按值返回对象是我能想到的避免返回句柄的唯一方法。这对我来说表明我应该尽可能按值返回私有对象内部。

但是,要按值返回对象,这需要与“DISALLOW_COPY_AND_ASSIGN”运算符的Google C++ Style Guide 相悖的复制构造函数。

作为一个 C++ 新手,除非我遗漏了什么,否则我发现这两个建议相互冲突。

所以我的问题是:是否没有灵丹妙药可以让有效的引用返回到不受悬空指针影响的对象内部? const 引用返回是否尽可能好?另外,我应该经常使用私有对象字段的指针吗?选择何时按值或按指针存储对象的私有实例字段的一般经验法则是什么?

(编辑)为澄清起见,Meyers 的示例悬空指针代码:

class Rectangle {
public:
  const Point& upperLeft() const { return pData->ulhc; }
  const Point& lowerRight() const { return pData->lrhc; }
  ...
};

class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj); 

如果客户端创建一个函数,代码如下:

GUIObject *pgo; // point to some GUIObject
const Point *pUpperLeft = &(boundingBox(*pgo).upperLeft());

“对 boundingBox 的调用将返回一个新的临时 Rectangle 对象 [(从这里调用 temp。)] upperLeft 然后将在 temp 上调用,并且该调用将返回对 temp 内部部分的引用,特别是,到组成它的点之一...在语句的末尾,boundingBox的返回值temp将被破坏,这将间接导致temp的点被破坏。反过来,这将使pUpperLeft指向一个对象那已经不存在了。” Meyers,Effective C++(第 28 条)

我认为他建议按值返回 Point 来避免这种情况:

const Point upperLeft() const { return pData->ulhc; }

【问题讨论】:

  • 哦,那我不同意迈耶斯的观点。通过 const 引用而不是按值返回 Point 只是因为客户端可以滥用这些函数并且由于销毁 Rectangle 而使引用无效,这并不是按值返回所有内容的理由。在这种情况下,客户应该知道他需要返回的点的副本(当您通过 const 引用返回时他仍然可以这样做)。我最喜欢迈耶斯,但这本书已经有 12 年的历史了。在这种情况下,Sutter 会反驳他,并建议 Point 应该通过 const 引用返回 [...]
  • [...] 避免“悲观化”。然而,如果这就像一个窗口句柄被返回给一个控件,你通常希望避免返回它,因为它会放弃你的类维护不变量的能力。

标签: c++ pointers encapsulation


【解决方案1】:

Google C++ 风格指南,容我们说,有点“特别”,并引发了关于各种 C++ 新闻组的大量讨论。就这样吧。

在一般情况下,我建议遵循 Effective C++ 中的指导原则通常被认为是一件好事;在您的特定情况下,返回一个对象而不是对内部对象的任何类型的引用通常是正确的做法。大多数编译器都非常擅长处理大返回值(谷歌返回值优化,几乎每个编译器都这样做)。

如果使用分析器进行的测量表明返回值正在成为瓶颈,那么我会考虑替代方法。

【讨论】:

  • 呸!根据 Wikipedia 的说法,More Effective C++ 似乎详细讨论了这一点(我还没有。)在敲出生产就绪代码之前,有很多关于 C++ 怪癖的知识是残酷的。不过,谢谢你:)
  • 值得注意的是,使用 RVO 的编译器通常使用它来优化不必要的临时复制。对于按值返回 UDT 的简单访问器,他们不一定会在优化复制方面做得很好。
【解决方案2】:

首先,让我们在上下文中看一下这句话:

根据 Effective C++(第 28 条), “避免返回句柄(参考, 指针或迭代器)指向对象 内部结构。它增加了封装, 帮助 const 成员函数起作用 const,并最小化 悬垂的把手。”

这基本上是在谈论一个类保持不变量(粗略地说,保持不变的属性)的能力。

假设您有一个按钮小部件包装器,Button,它存储了按钮的特定于操作系统的窗口句柄。如果使用该类的客户端可以访问内部句柄,他们可以使用特定于操作系统的调用来篡改它,例如销毁按钮、使其不可见等。基本上通过返回此句柄,您的 Button 类牺牲了它最初对按钮句柄的任何控制。

您希望通过在此 Button 类中提供您可以对按钮执行的所有操作作为方法来避免此类 Button 类中的这些情况。那么你就不需要返回一个特定于操作系统的按钮句柄的句柄了。

不幸的是,这在实践中并不总是有效。有时,由于各种原因,您必须通过引用返回句柄或指针或其他一些内部。让我们以 boost::scoped_ptr 为例。它是一个智能指针,旨在通过它存储的内部指针来管理内存。它有一个返回此内部指针的 get() 方法。不幸的是,这允许客户执行以下操作:

delete my_scoped_ptr.get(); // wrong

尽管如此,这种妥协是必要的,因为在许多情况下,我们正在使用需要传入常规指针的 C/C++ API。为了满足不接受您的特定类但确实接受的库,通常需要妥协它的内部结构之一。

在您的情况下,请尝试考虑您的类是否可以通过提供函数来执行您希望通过公共接口对内部执行的所有操作来避免以这种方式返回内部。如果没有,那么你已经做了所有你能做的;您必须返回指向它的指针/引用,但将其记录为特殊情况将是一个好习惯。如果您知道哪些地方需要提前访问班级内部,您还应该考虑使用朋友;通过这种方式,您可以将此类访问器方法保持为私有且其他人无法访问。

按值返回对象是唯一的 我能想到的避免返回的方法 手柄。这对我来说表明我应该 通过返回私有对象内部 尽可能的值。

不,如果你可以返回一个副本,那么你同样可以通过 const 引用返回。客户不能(在正常情况下)篡改此类内部结构。

【讨论】:

  • 谢谢!尤其是在明智地使用朋友方面。我知道 const 引用不允许篡改私有内部,但这让我很担心:“句柄是指针、引用还是迭代器都没有关系。它是否符合 const 并不重要。返回句柄的成员函数本身是否是 const 并不重要。重要的是正在返回句柄,因为一旦完成,您将面临句柄将比它所引用的对象寿命更长的风险。这种危险是不可避免的吗?在这种情况下我应该使用 shared_ptr 吗?
  • @Glitz 这取决于句柄的含义。如果句柄是一个指针或允许可变访问的东西,那么以任何方式返回它都会允许客户端篡改内部结构。但是,我很确定 Meyers 使用术语“句柄”来处理这种特殊的可变情况。例如,返回 int* const 不会阻止用户篡改整数指针。您能否提供有关您的特定案例的更多信息?也许是一些示例代码?
  • 如果在这个意义上它是一个句柄,那么按值返回它同样是有问题的,因为句柄的副本基本上仍然指向同一个东西。避免这种情况的唯一方法是完全避免以任何形状或形式返回句柄,并让您的班级全权负责使用它做任何事情。如果您无法避免这种情况,那么您必须做出妥协(但仍然考虑朋友,如果这不起作用,至少记录该功能,以便客户不会只想使用句柄而忽略您的课程) .
  • 我在问题中添加了 Meyers 的示例代码以进行澄清
【解决方案3】:

这真的取决于情况。如果您打算查看要通过引用传递的调用方法的更改。请记住,按值传递是一项非常繁重的操作。它需要调用复制构造函数,本质上它必须分配和存储足够的内存以适应对象的大小。

您可以做的一件事是假传值。这意味着将实际参数按值传递给接受 const 对象的方法。这当然意味着调用者不关心你的对象的变化。

尽量限制传值,除非必须这样做。

【讨论】:

    猜你喜欢
    • 2011-12-21
    • 2016-07-07
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 1970-01-01
    • 2020-05-15
    • 1970-01-01
    • 2023-02-09
    相关资源
    最近更新 更多