簡體   English   中英

非成員非朋友函數語法

[英]non-member non-friend function syntax

他們是否可以使用與成員函數相同的“點”表示法在對象上使用非成員非友元函數?

我可以從一個類中拉出(任何)成員,並讓用戶以他們一直使用的方式使用它嗎?

更長的解釋:

Scott Meyers ,Herb Sutter等人認為,非成員非朋友函數是對象接口的一部分,可以改進封裝。 我同意他們。

但是,在最近閱讀本文之后: http//www.gotw.ca/gotw/084.htm我發現自己質疑語法含義。

在那篇文章中,Herb建議使用一個inserterasereplace成員,以及幾個同名的非成員非朋友函數。

這是否意味着,正如我認為的那樣,Herb認為某些函數應該與點符號一起使用,其他函數應該作為全局函數使用?

std::string s("foobar");

s.insert( ... ); /* One like this */
insert( s , ...); /* Others like this */

編輯:

感謝大家提供的非常有用的答案,但是,我認為我的問題的觀點被忽略了。

我特別沒有提到運營商的具體情況,以及他們如何保留“自然”符號。 也不應該將所有內容都包裝在命名空間中。 這些東西寫在我鏈接的文章中。

問題本身是:

在文章中,Herb建議一個insert()方法是成員,而其余的是非成員非朋友函數。

這意味着要使用一種形式的insert(),你必須使用點符號,而對於其他形式,則不需要。

這只是我,還是聽起來很瘋狂?

我有一種預感,也許你可以使用單一語法。 (我在想如何Boost :: function可以為mem_fun取一個* this參數)。

是的,這意味着對象的部分接口由非成員函數組成。

對於類T的對象,你是否正確使用以下符號的事實:

void T::doSomething(int value) ;     // method
void doSomething(T & t, int value) ; // non-member non-friend function

如果你想讓doSomething函數/方法返回void,並有一個名為“value”的int參數。

但有兩件事值得一提。

第一個是類的接口的函數部分應該在同一個命名空間中。 這是另一個原因(如果需要另一個原因)使用命名空間,如果只是“組合”一個對象和作為其接口一部分的函數。

好的方面是它促進了良好的封裝。 但不好的一點是它使用了類似功能的符號我個人不喜歡。

第二是運營商不受此限制。 例如,類T的+ =運算符可以用兩種方式編寫:

T & operator += (T & lhs, const T & rhs) ;
{
   // do something like lhs.value += rhs.value
   return lhs ;
}

T & T::operator += (const T & rhs) ;
{
   // do something like this->value += rhs.value
   return *this ;
}

但這兩種符號用作:

void doSomething(T & a, T & b)
{
   a += b ;
}

從美學的角度來看,這比功能性的符號要好得多。

現在,能夠從同一個界面編寫一個函數,並且仍然能夠通過“。”調用它將是一個非常酷的語法糖。 michalmocny提到的符號,就像在C#中一樣。

編輯:一些例子

讓我們說,無論出於何種原因,我想要創建兩個“類似整數”的類。 第一個是IntegerMethod:

class IntegerMethod
{
   public :
      IntegerMethod(const int p_iValue) : m_iValue(p_iValue) {}
      int getValue() const { return this->m_iValue ; }
      void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }

      IntegerMethod & operator += (const IntegerMethod & rhs)
      {
         this->m_iValue += rhs.getValue() ;
         return *this ;
      }

      IntegerMethod operator + (const IntegerMethod & rhs) const
      {
         return IntegerMethod (this->m_iValue + rhs.getValue()) ;
      }

      std::string toString() const
      {
         std::stringstream oStr ;
         oStr << this->m_iValue ;
         return oStr.str() ;
      }

   private :
      int m_iValue ;
} ;

這個類有6個方法可以訪問它的內部。

第二個是IntegerFunction:

class IntegerFunction
{
   public :
      IntegerFunction(const int p_iValue) : m_iValue(p_iValue) {}
      int getValue() const { return this->m_iValue ; }
      void setValue(const int p_iValue) { this->m_iValue = p_iValue ; }

   private :
      int m_iValue ;
} ;

IntegerFunction & operator += (IntegerFunction & lhs, const IntegerFunction & rhs)
{
   lhs.setValue(lhs.getValue() + rhs.getValue()) ;
   return lhs ;
}

IntegerFunction operator + (const IntegerFunction & lhs, const IntegerFunction & rhs)
{
   return IntegerFunction(lhs.getValue() + rhs.getValue()) ;
}

std::string toString(const IntegerFunction & p_oInteger)
{
   std::stringstream oStr ;
   oStr << p_oInteger.getValue() ;
   return oStr.str() ;
}

它只有3種方法,這樣可以減少可以訪問其內部的代碼數量。 它有3個非會員非朋友功能。

這兩個類可以用作:

void doSomething()
{
   {
      IntegerMethod iMethod(25) ;
      iMethod += 35 ;
      std::cout << "iMethod   : " << iMethod.toString() << std::endl ;

      IntegerMethod result(0), lhs(10), rhs(20) ;
      result = lhs + 20 ;
      // result = 10 + rhs ; // WON'T COMPILE
      result = 10 + 20 ;
      result = lhs + rhs ;
   }

   {
      IntegerFunction iFunction(125) ;
      iFunction += 135 ;
      std::cout << "iFunction : " << toString(iFunction) << std::endl ;

      IntegerFunction result(0), lhs(10), rhs(20) ;
      result = lhs + 20 ;
      result = 10 + rhs ;
      result = 10 + 20 ;
      result = lhs + rhs ;
   }
}

當我們比較運算符的使用(“+”和“+ =”)時,我們發現將運算符作為成員或非成員使用它的表面用法沒有區別。 不過,有兩點不同:

  1. 該成員可以訪問其所有內部。 非成員必須使用公共成員方法

  2. 從一些二元運算符,如+,*,有類型提升很有意思,因為在一種情況下(即lhs提升,如上所示),它不適用於成員方法。

現在,如果我們比較非運算符使用(“toString”),我們看到成員非運算符使用對於類Java開發人員而言比非成員函數更“自然”。 盡管不熟悉,但對於C ++來說,重要的是要接受它,盡管它的語法,非成員版本從OOP角度來看更好,因為它無法訪問類內部。

作為獎勵:如果要將操作符(或非操作符函數)添加到沒有操作符的對象(例如,<windows.h>的GUID結構),則可以,而無需修改結構本身。 對於運算符,語法將是自然的,對於非運算符,則...

沒有辦法用點表示法寫一個非成員非朋友,即因為“運算符”。 不能超載。

您應始終將非成員非友元類包裝在匿名命名空間中(如果只有當前轉換單元需要這些函數),或者在用戶的某個有意義的命名空間中。

但是,在最近閱讀本文之后: http//www.gotw.ca/gotw/084.htm我發現自己質疑語法含義。

語法含義可以在編寫良好的C ++庫中隨處可見:C ++在所有地方都使用免費函數。 這對於具有OOP背景的人來說是不尋常的,但它是C ++中的最佳實踐。 例如,考慮STL標頭<algorithm>

因此,使用點符號成為規則的例外,而不是相反。

請注意,其他語言選擇其他方法; 這導致在C#和VB中引入了“擴展方法”,允許模擬靜態函數的方法調用語法(即你想到的正是這樣)。 然后,C#和VB是嚴格的面向對象語言,因此對方法調用使用單個符號可能更重要。

除此之外,函數總是屬於命名空間 - 雖然我自己偶爾會違反這個規則(但只在一個編譯單元中,即我相當於main.cpp ,它不起作用)。

就個人而言,我喜歡自由功能的可擴展性。 size函數就是一個很好的例子:

// joe writes this container class:
namespace mylib {
    class container { 
        // ... loads of stuff ...
    public:
        std::size_t size() const { 
            // do something and return
        }
    };

    std::size_t size(container const& c) {
        return c.size();
    } 
}

// another programmer decides to write another container...
namespace bar {
    class container {
        // again, lots of stuff...
    public:
        std::size_t size() const {
            // do something and return
        }
    };

    std::size_t size(container const& c) {
        return c.size();
    } 
}

// we want to get the size of arrays too
template<typename T, std::size_t n>
std::size_t size(T (&)[n]) {
    return n;
}

現在考慮使用free size函數的代碼:

int main() {
    mylib::container c;
    std::size_t c_size = size(c);

    char data[] = "some string";
    std::size_t data_size = size(data);
}

如您所見,您可以使用size(object)而無需關心類型所在的命名空間(取決於參數類型,編譯器會計算出命名空間本身),而無需關心幕后發生的事情。 考慮使用像beginend一樣的自由函數。 這正是boost::range 所做的

可以使用單一語法,但可能不是您喜歡的語法。 不要在類范圍內放置一個insert(),而是將其作為類的朋友。 現在你可以寫了

mystring s;
insert(s, "hello");
insert(s, other_s.begin(), other_s.end());
insert(s, 10, '.');

對於任何非虛擬的公共方法,它等同於將其定義為非成員友元函數。 如果混合點/無點語法困擾你,那么一定要使這些方法成為友方函數。 沒有區別。

將來我們也可以編寫這樣的多態函數,所以也許這是C ++方式,而不是人為地試圖強制自由函數進入點語法。

如果你想保留點符號,但也需要單獨的函數,這些函數不需要成為類中的朋友(因此他們無法訪問私有成員從而破壞封裝),你可能會編寫一個mixin類。 要么在mixin中使“常規”插入純虛擬,要么將其保持為非虛擬並使用CRTP:

template<typename DERIVED, typename T>
struct OtherInsertFunctions {
    void insertUpsideDown(T t) {
        DERIVED *self = static_cast<DERIVED*>(this);
        self->insert(t.turnUpsideDown());
    }
    void insertBackToFront(T t) // etc.
    void insert(T t, Orientation o) // this one is tricksy because it's an
                                    // overload, so requires the 'using' declaration
};

template<typename T>
class MyCollection : public OtherInsertFunctions<MyCollection,T> {
public:
    // using declaration, to prevent hiding of base class overloads
    using OtherInsertFunctions<MyCollection,T>::insert;
    void insert(T t);
    // and the rest of the class goes here
};

無論如何,這樣的事情。 但正如其他人所說,C ++程序員並不“假定”反對自由函數,因為你“應該”總是在尋找編寫泛型算法的方法(比如std :: sort)而不是添加成員函數特別的課程。 讓一切始終如一的方法更具有Java-y。

是的,它們應該是全局的或命名空間范圍的。 非成員非朋友函數在C#中看起來更漂亮,它們使用點表示法(它們被稱為擴展方法 )。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM