C++ &引用;避免将句柄返回到对象内部;,那么';还有什么选择?

C++ &引用;避免将句柄返回到对象内部;,那么';还有什么选择?,c++,effective-c++,C++,Effective C++,在第5章第28项中说明避免将“句柄”(指针、引用或迭代器)返回到对象内部,这无疑是一个很好的观点 也就是说,不要这样做: class Family { public: Mother& GetMother() const; } 因为它破坏了封装并允许更改私有对象成员 甚至不要这样做: class Family { public: const Mother& GetMother() const; } 因为它可能导致“悬空句柄”,这意味着您保留对已销毁对象的成员的引

在第5章第28项中说明避免将“句柄”(指针、引用或迭代器)返回到对象内部,这无疑是一个很好的观点

也就是说,不要这样做:

class Family
{
public:
    Mother& GetMother() const;
}
因为它破坏了封装并允许更改私有对象成员

甚至不要这样做:

class Family
{
public:
    const Mother& GetMother() const;
}
因为它可能导致“悬空句柄”,这意味着您保留对已销毁对象的成员的引用

现在,我的问题是,有什么好的选择吗?想象一下妈妈很重!如果我现在返回一个母亲的副本而不是一个引用,那么GetMother将成为一个成本相当高的操作


如何处理此类情况?

这可以追溯到一个基本的OO原则:

Tell objects what to do rather than doing it for them.

你需要母亲做些有用的事吗?请
家庭
对象为您执行此操作。通过在<代码>家庭> <代码>对象上的方法参数,将它封装在一个漂亮的接口(C++中的代码>类<代码> >中。任何一个只读的视图是你所要处理的,并且由于某种原因,你需要避免悬空句柄,然后你可以考虑返回一个<代码> SyddypTr> /COD> 这样,
母亲
对象就可以比
家庭
对象活得更久。当然,它还必须通过共享存储

部分考虑因素是您是否要使用过多的
shared\u ptr
s来创建引用循环。如果你是,那么你可以考虑<代码>弱小的PTR < /代码>,你也可以考虑只接受挂起手柄的可能性,但是编写客户端代码来避免它。例如,没有人太担心
std::vector::at
返回的引用在向量被销毁时变得过时。但是,容器是故意公开它“拥有”的对象的类的极端例子

因为它会导致“摇摇晃晃的把手”,这意味着你保持一个 对已销毁对象的成员的引用


您的用户也可以取消引用
null
或其他同样愚蠢的东西,但他们不会这样做,而且只要生命周期明确且定义良好,他们也不会这样做。这没有什么不对。

可能的解决方案取决于你的类的实际设计,你认为“对象内部”是什么?

  • Mother
    只是
    Family
    的实现细节,可以对
    Family
    用户完全隐藏
  • 被视为其他公共对象的组成部分
  • 在第一种情况下,您应完全封装子对象,并仅通过
    系列
    功能成员(可能复制
    母接口
    公共接口)提供对其的访问:

    嗯,如果
    母亲
    界面很大,可能会很无聊,但这可能是设计问题(好的界面应该有3-5-7个成员),这将使您以更好的方式重新访问和重新设计它

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

  • 封装分解(最终用户代码将取决于
    Mother
    定义)
  • 所有权问题(悬空指针/引用)
  • 要解决问题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;
       ...
    };
    
    class-IMother
    {
    虚拟std::string GetName()const=0;
    ...
    };
    班长:公共课
    {
    //IMother和其他东西的实现
    ...
    };
    阶级家庭
    {
    std::shared_ptr GetMother()常量{return mommy;}
    std::weakptrgetmotherweakptr()常量{return mommy;}
    ...
    私人:
    std::共享\u ptr妈咪;
    ...
    };
    
    这只是一个语义问题。在您的例子中,
    Mother
    不是
    Family
    内部,而不是它的实现细节<代码>母类类实例可以在
    以及许多其他实体中引用。此外,
    母亲
    实例寿命甚至可能与
    家族
    寿命不相关


    因此,更好的设计应该是在
    系列
    中存储一个
    共享的ptr
    ,并在
    系列
    界面中无需担心地公开它。

    首先,让我重申一下:最大的问题不是生存期问题,而是封装问题

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

    现在,您返回的引用是否为
    const
    并不重要:您意外地暴露了这样一个事实,即您在
    家族
    类中有一个
    母亲
    对象,而现在您无法摆脱它(即使您有更好的表示)因为您的所有客户都可能依赖它,并且必须更改他们的代码

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

    class Family {
    public:
    
        Mother mother() { return _mother; }
        void mother(Mother m) { _mother = m; }
    
    private:
        Mother _mother;
    };
    
    因为在下一次迭代中,我可以删除
    \u mother
    ,而不会破坏接口:

    class Family
    {
      std::string GetMotherName() const { return mommy.GetName(); }
      unsigned GetMotherAge() const { return mommy.GetAge(); }
      ...
    private:
       Mother mommy;
       ...
    };
    
    class Family {
    public:
    
         Mother mother() { return Mother(_motherName, _motherBirthDate); }
    
         void mother(Mother m) {
             _motherName = m.name();
             _motherBirthDate = m.birthDate();
         }
    
    private:
         Name _motherName;
         BirthDate _motherBirthDate;
    };
    
    看看我是如何在不改变接口的情况下彻底改造内部结构的?简单的豌豆

    注:显然,此转换仅用于效果


    显然,这种封装是以性能为代价的,这里有一种紧张感,这是你在每次编写getter时应该优先选择封装还是性能的判断。

    这取决于我想访问这个东西的原因……我正在努力避免对“母亲很重”进行明智的抨击:p这是一个
    std::shared_ptr
    ?实际上,你没有抓住要点。这与其说是一个生命周期的问题(尽管也有这个问题),更重要的是对封装的破坏:如果您想替换
    M,该怎么办