繁体   English   中英

非成员函数如何改进封装

[英]How Non-Member Functions Improve Encapsulation

我阅读了Scott Meyers关于这个主题的文章 ,并对他所谈论的内容感到很困惑。 我这里有3个问题。

问题1

为了详细解释,假设我正在使用push_backinsert和operator []等方法编写一个简单的vector<T>类。 如果我遵循Meyers的算法,我最终会得到所有非会员朋友的功能。 我将有一个带有很少私有成员和许多非成员朋友函数的向量类。 这是他在说什么?

问题2

我仍然不了解非成员函数如何改进封装。 考虑一下迈耶斯文章中给出的代码。

class Point {
public:
   int getXValue() const; 
   int getYValue() const; 
   void setXValue(int newXValue);
   void setYValue(int newYValue);

private:
  ...                 // whatever...
};

如果遵循他的算法, setXXXX方法应该是非成员。 我的问题是如何增加封装? 他还说

我们现在已经看到,衡量一个类中封装量的合理方法是计算在类的实现发生变化时可能会破坏的函数数量。

直到我们在类实现发生变化时保持方法签名不变,没有客户端代码会被破坏并且封装得很好,对吧? 这同样适用于非成员函数。 那么非成员函数提供的优势是什么?

问题3

引用他的算法

else if (f needs type conversions
         on its left-most argument)
   {
   make f a non-member function;
   if (f needs access to non-public
       members of C)
      make f a friend of C;
   }

他的意思是f需要在最左边的参数上进行类型转换 他还在文章中说了以下内容。

此外,我们现在看到“朋友功能违反封装”的共同主张并不完全正确。 朋友不会违反封装,他们只是减少它 - 与成员函数完全相同。

这和上面的算法是矛盾的吧?

问题1

在这种情况下,遵循Meyers的算法将为您提供成员函数:

  • 他们需要虚拟吗? 没有。
  • 他们是operator<<operator>> 没有。
  • 他们需要类型转换吗? 没有。
  • 它们可以在公共接口方面实现吗? 没有。
  • 所以让他们成员。

他的建议是只在他们真正需要的时候才能成为他们的朋友; 支持非会员非朋友而不是朋友。

问题2

SetXXXX函数需要访问类的内部(私有)表示,因此它们不能是非成员非朋友; 所以,迈耶斯认为,他们应该是成员而不是朋友。

通过隐藏类的实现细节来实现封装; 您可以与私有实现分开定义公共接口。 如果您随后发明了更好的实现,则可以在不更改公共接口的情况下进行更改,并且使用该类的任何代码都将继续工作。 因此,Meyers的“可能被破坏的函数数量”计算成员和朋友函数(我们可以通过查看类定义轻松跟踪),但不是通过其公共接口使用该类的任何非成员非朋友函数。

问题3

这已得到回答

从迈耶斯的建议中取消的重点是:

  • 设计类使其具有与其私有实现分离的干净,稳定的公共接口;
  • 只有在真正需要访问实现时才能成为函数成员或朋友;
  • 只有当他们不能成为会员时才能成为朋友。

意义f需要在其上进行类型转换 - 最左边的arg如下:

考虑以下senario:

Class Integer 
{  
    private: 
       int num;
     public:
        int getNum( return num;)
        Integer(int n = 0) { num = n;}

    Integer(const Integer &rhs)) { num = rhs.num ;}
    Integer operator * (const Integer &rhs)
    {
         return Integer(num * rhs.num);
    }
}


int main()
{
    Integer i1(5);

    Integer i3 = i1 *  3; // valid 

    Integer i3 = 3 * i1 ; // error     
}

在上面的代码中, i3 = i1 * 3相当于this->operator*(3) ,它有效,因为3被隐式转换为Integer。

如果稍后i3 = 3 * i1相当于3.operator*(i1) ,根据规则当u重载运算符使用成员函数时,调用对象必须是同一个类。 但这不是那个。

要使Integer i3 = 3 * i1工作,可以定义非成员函数如下:

Integer operator * (const Integer &lhs , const Integer &rhs) // non-member function
    {

         return Integer(lhs.getNum() * rhs.getNum());

    }

我想你会从这个例子中得到一个想法.....

在他提出的非成员函数的四个案例中,你提出的vector方法最接近的是这个:

else if (f can be implemented via C's
         public interface)
   make f a non-member function;

但是你不能通过公共接口实现push_backinsertoperator[]等方法。 这些公共界面。 有可能在insert方面实现push_back ,但在很大程度上,您将使用哪种公共接口用于此类方法?

此外,为非成员函数提供友谊的情况实际上是特殊情况,因为我看到它, operator<<operator>>和类型转换,都需要来自类的非常准确和未过滤的数据。 这些方法自然是非常侵入性的。

虽然我不是Dobbs博士或任何声称的“C ++大师”的粉丝,但我想在这种情况下你可能会双重猜测你自己的实现。 Scott Meyer的算法对我来说似乎很合理。

仔细看看STL算法。 sortcopytransform等对迭代器进行操作,而不是成员函数。

你的算法也错了。 使用Point的公共接口无法实现set和get函数。

问题2

如果你记得,斯科特迈耶斯也提出了以下建议:

- >保持类接口完整和最小化。

请参阅以下方案:

class Person {

private: string name;

         unsigned int age;

         long salary;

public:

       void setName(string);// assme the implementation

       void setAge(unsigned int); // assme the implementation

       void setSalary(long sal); // assme the implementation

       void setPersonData()
       {
           setName("Scott");
           setAge(25);
           selSalary(50000);
        }
}

这里的setPersonData()是成员函数,但最终它的作用也可以通过使它成为非成员函数来实现,它将使类的接口保持最小,并且不会不必要地充满成员函数的类。

   void setPersonData(Person &p) 
   {           
       p.setName("Scott");
       p.setAge(25);
       p.selSalary(50000);
    }

我想一般的观点是,如果可以的话,总是用其他东西来实现它是有益的。 将功能实现为非朋友自由函数,可确保在更改类表示时此功能不会中断。

在现实生活中,我想它可能有一个问题:您可能能够在当前实现的公共接口方面实现某些功能,但如果该类有更改,则可能不再可能(并且您'我需要开始宣布朋友的事情)。 (例如,当涉及到算法优化时,自由函数可能会受益于一些额外的缓存数据,这些数据不应向公众公开。)

所以我从中得出的指导原则是:使用常识,但不要害怕自由功能。 它们不会使您的C ++代码更少面向对象。


另一件事可能是一个完全由getter和setter组成的接口。 这几乎不包含任何东西。

特别是在Point的情况下,您可能会试图将数据存储为int coords[2] ,而在这方面,getter和setter可能有意义(但是也可能总是考虑使用的易用性和易用性实现)。

但是如果你转向更复杂的类,他们应该一些事情(一些核心功能),而不仅仅是访问他们的数据。


当涉及到vector时,它的一些方法可能是自由函数:assign(在clear + insert方面),at,back,front(就size size + operator[] ),empty(就大小或开始而言) / end), pop_back (擦除+大小), push_back (插入+大小),结束(开始+大小),rbegin和rend(开始和结束)。

但如果采取严格措施,这可能会导致相当混乱的界面,例如

 for (vector<T>::iterator it = v.begin(); it != end(v); ++it)

此外,这里必须考虑其他容器的功能。 如果std :: list不能作为自由函数实现end,那么std :: vector也不应该(模板需要一个统一模式来迭代容器)。

再次,使用常识。

他特别说“非会员非朋友职能”(强调我的)。 如果你需要将非成员函数作为恶魔,他的算法说它应该是一个成员函数,除非它是operator >>或operator <<或者需要在其最左边的参数上进行类型转换。

直到我们在类实现发生变化时保持方法签名不变,没有客户端代码会被破坏并且封装得很好,对吧? 这同样适用于非成员函数。 那么非成员函数提供的优势是什么?

Meyers说,具有许多方法的类比具有更少方法的类封装更少,因为所有这些内部方法的实现都会发生变化。 如果任何方法可能是非成员,那么将减少可能受到类内部更改影响的方法的数量。

他的意思是f需要在最左边的参数上进行类型转换?

我认为他指的是运算符,如果它们是成员函数,则会有一个隐含的最左边的参数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM