"避免将句柄返回到对象内部",那么替代方案是什么?

sch*_*chc 13 c++ effective-c++

Scott Meyers的有效C++在第5章第28项中告诉我们避免将"句柄"(指针,引用或迭代器)返回到对象内部,这绝对是一个好点.

即不要这样做:

class Family
{
public:
    Mother& GetMother() const;
}
Run Code Online (Sandbox Code Playgroud)

因为它破坏了封装并允许改变私有对象成员.

甚至不这样做:

class Family
{
public:
    const Mother& GetMother() const;
}
Run Code Online (Sandbox Code Playgroud)

因为它可以导致"悬空手柄",这意味着您保留对已经销毁的对象的成员的引用.

现在,我的问题是,有什么好的选择吗?想象一下妈妈很沉重!如果我现在返回母亲的副本而不是参考,GetMother正在成为一个相当昂贵的操作.

你如何处理这种情况?

Mat*_* M. 14

首先,让我重新迭代:最大的问题不是生命周期,而是封装之一.

封装不仅意味着没有人能够在没有意识到内容的情况下修改内部,封装意味着没有人知道如何在类中实现内容,因此只要保持接口相同,就可以随意更改类内部.

现在,您返回的引用是否与之const无关:您不小心暴露了您的Mother内部有一个对象的事实Family类中现在你无法摆脱它(即使你有更好的表示),因为所有你的客户可能依赖它并且必须更改其代码......

最简单的解决方案是按值返回:

class Family {
public:

    Mother mother() { return _mother; }
    void mother(Mother m) { _mother = m; }

private:
    Mother _mother;
};
Run Code Online (Sandbox Code Playgroud)

因为在下一次迭代中我可以在_mother不破坏界面的情况下删除:

class Family {
public:

     Mother mother() { return Mother(_motherName, _motherBirthDate); }

     void mother(Mother m) {
         _motherName = m.name();
         _motherBirthDate = m.birthDate();
     }

private:
     Name _motherName;
     BirthDate _motherBirthDate;
};
Run Code Online (Sandbox Code Playgroud)

看看我如何在不改变界面的情况下完全改造内部结构?十分简单.

注意:显然这种转换只是为了效果......

显然,这种封装是以某种性能为代价的,这里有一种紧张,每次你写一个吸气剂时,是否应该首选封装或性能是你的判断.


Ros*_*ost 6

可能的解决方案取决于您的类的实际设计以及您认为"对象内部"的内容.

  1. Mother只是实现细节,Family可以完全隐藏Family用户
  2. Family 被视为其他公共对象的组合

在第一种情况下,您应完全封装子对象并仅通过Family函数成员(可能复制Mother公共接口)提供对它的访问:

class Family
{
  std::string GetMotherName() const { return mommy.GetName(); }
  unsigned GetMotherAge() const { return mommy.GetAge(); }
  ...
private:
   Mother mommy;
   ...
};
Run Code Online (Sandbox Code Playgroud)

好吧,如果Mother界面非常大,可能会很无聊,但可能这是设计问题(好的界面应该有3-5-7个成员),这将使你以更好的方式重新审视和重新设计它.

在第二种情况下,您仍然需要返回整个对象.有两个问题:

  1. 封装细分(最终用户代码取决于Mother定义)
  2. 所有权问题(悬挂指针/参考)

要解决问题1使用界面而不是特定类,要解决问题2使用共享或弱所有权:

class IMother
{
   virtual std::string GetName() const = 0;
   ...
};

class Mother: public IMother
{
   // Implementation of IMother and other stuff
   ...
};

class Family
{
   std::shared_ptr<IMother> GetMother() const { return mommy; }
   std::weak_ptr<IMother> GetMotherWeakPtr() const { return mommy; }

   ...
private:
   std::shared_ptr<Mother> mommy;
   ...
};
Run Code Online (Sandbox Code Playgroud)

  • `GetWeakMother`妈妈妈妈并不弱! (2认同)

Cri*_*ole 3

这又回到了一个基本的 OO 原则:

Tell objects what to do rather than doing it for them.
Run Code Online (Sandbox Code Playgroud)

你需要Mother做一些有用的事情吗?要求Family对象为你做这件事。Class通过对象上方法的参数,将任何外部依赖项封装在一个漂亮的接口(在 C++ 中)中Family

  • 因此,你会得到函数“printMotherName”、“printFatherName”、“printMaternalGrandfatherName”等。有时,可以使用有用的抽象来削减这个丛林,当然,例如“printNameOfRelative(RelationShip)”。 (4认同)
  • 是的,并且热情地应用了 Demeter 定律,禁止“file.getLine(0).rstrip()”,因为 File 接口的用户不应该因此耦合到 String 接口。所以你改写“file.getrstrippedLine(0)”。当 Family 界面的用户可以避免同时使用 Mother 界面时,您必须做出判断。如果可行的话,就去做吧。 (2认同)