簡體   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