名稱空間
變體
操作

非靜態成員函式

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

非靜態成員函式是在類的成員宣告中宣告的函式,沒有 staticfriend 說明符(有關這些關鍵字的作用,請參見靜態成員函式友元宣告)。

class S
{
    int mf1(); // non-static member function declaration
    void mf2() volatile, mf3() &&; // can have cv-qualifiers and/or a reference-qualifier
        // the declaration above is equivalent to two separate declarations:
        // void mf2() volatile;
        // void mf3() &&;
 
    int mf4() const { return data; } // can be defined inline
    virtual void mf5() final; // can be virtual, can use final/override
    S() : data(12) {} // constructors are member functions too
    int data;
};
 
int S::mf1() { return 7; } // if not defined inline, has to be defined at namespace

建構函式解構函式轉換函式使用特殊的語法進行宣告。本頁描述的規則可能不適用於這些函式。詳情請參見各自的頁面。

顯式物件成員函式是沒有顯式物件引數的非靜態成員函式。

(C++23 起)

隱式物件成員函式是沒有顯式物件引數的非靜態成員函式(在 C++23 之前,這是唯一一種非靜態成員函式,因此在文獻中被稱為“非靜態成員函式”)。

目錄

[編輯] 說明

允許任何函式宣告,並附加僅適用於非靜態成員函式的語法元素:純說明符、cv 限定符、引用限定符、finaloverride 說明符(C++11 起)以及成員初始化列表

X 的非靜態成員函式可以按以下方式呼叫:

1) 對於型別為 X 的物件,使用類成員訪問運算子
2) 對於從 X 派生的類的物件
3) 直接從 X 的成員函式體內
4) 直接從從 X 派生的類的成員函式體內

在不是型別 X 或從 X 派生的型別的物件上呼叫類 X 的非靜態成員函式會觸發未定義行為。

X 的非靜態成員函式體內,任何解析為 XX 的基類的非型別非靜態成員的id-expression e(例如識別符號)都會轉換為成員訪問表示式 (*this).e(除非它已經是成員訪問表示式的一部分)。這不會發生在模板定義上下文中,因此名稱可能需要顯式地加上 this-> 字首才能成為依賴名稱

struct S
{
    int n;
    void f();
};
 
void S::f()
{
    n = 1; // transformed to (*this).n = 1;
}
 
int main()
{
    S s1, s2;
    s1.f(); // changes s1.n
}

X 的非靜態成員函式體內,任何解析為 XX 的基類的靜態成員、列舉器或巢狀型別的 unqualified-id 都會轉換為相應的 qualified-id。

struct S
{
    static int n;
    void f();
};
 
void S::f()
{
    n = 1; // transformed to S::n = 1;
}
 
int main()
{
    S s1, s2;
    s1.f(); // changes S::n
}

[編輯] 帶 cv 限定符的成員函式

隱式物件成員函式可以宣告為帶cv 限定符序列(constvolatile,或 constvolatile 的組合),該序列出現在函式宣告的引數列表之後。具有不同 cv 限定符序列(或沒有序列)的函式具有不同的型別,因此可以相互過載。

在帶 cv 限定符序列的函式體內,*this 是 cv 限定的,例如,在帶 const 限定符的成員函式中,通常只能呼叫其他帶 const 限定符的成員函式。如果應用了const_cast或透過不涉及this的訪問路徑,仍然可以呼叫不帶 const 限定符的成員函式。

#include <vector>
 
struct Array
{
    std::vector<int> data;
    Array(int sz) : data(sz) {}
 
    // const member function
    int operator[](int idx) const
    {                     // the this pointer has type const Array*
        return data[idx]; // transformed to (*this).data[idx];
    }
 
    // non-const member function
    int& operator[](int idx)
    {                     // the this pointer has type Array*
        return data[idx]; // transformed to (*this).data[idx]
    }
};
 
int main()
{
    Array a(10);
    a[1] = 1;  // OK: the type of a[1] is int&
    const Array ca(10);
    ca[1] = 2; // Error: the type of ca[1] is int
}

帶引用限定符的成員函式

隱式物件成員函式可以宣告為不帶引用限定符,帶左值引用限定符(引數列表後的標記 &)或右值引用限定符(引數列表後的標記 &&)。在過載決議期間,帶類 X 的 cv 限定符序列的隱式物件成員函式按以下方式處理:

  • 不帶引用限定符:隱式物件引數的型別是 cv 限定 X 的左值引用,並且還允許繫結右值隱含物件引數
  • 左值引用限定符:隱式物件引數的型別是 cv 限定 X 的左值引用
  • 右值引用限定符:隱式物件引數的型別是 cv 限定 X 的右值引用
#include <iostream>
 
struct S
{
    void f() &  { std::cout << "lvalue\n"; }
    void f() && { std::cout << "rvalue\n"; }
};
 
int main()
{
    S s;
    s.f();            // prints "lvalue"
    std::move(s).f(); // prints "rvalue"
    S().f();          // prints "rvalue"
}

注意:與 cv 限定不同,引用限定不會改變this指標的屬性:在右值引用限定函式中,*this 仍然是一個左值表示式。

(C++11 起)

[編輯] 虛擬函式和純虛擬函式

非靜態成員函式可以宣告為虛擬函式純虛擬函式。詳情請參見虛擬函式抽象類

顯式物件成員函式

對於未宣告 cv 限定符或引用限定符的非靜態非虛成員函式,其第一個引數(如果不是函式引數包)可以是顯式物件引數(用字首關鍵字 this 表示)

struct X
{
    void foo(this X const& self, int i); // same as void foo(int i) const &;
//  void foo(int i) const &; // Error: already declared
 
    void bar(this X self, int i); // pass object by value: makes a copy of “*this”
};

對於成員函式模板,顯式物件引數允許推導型別和值類別,此語言特性稱為“推導 this

struct X
{
    template<typename Self>
    void foo(this Self&&, int);
};
 
struct D : X {};
 
void ex(X& x, D& d)
{
    x.foo(1);       // Self = X&
    move(x).foo(2); // Self = X
    d.foo(3);       // Self = D&
}

這使得可以消除 const 和非 const 成員函式的重複,請參閱陣列下標運算子以獲取示例。

在顯式物件成員函式的函式體內,不能使用 this 指標:所有成員訪問都必須透過第一個引數完成,就像在靜態成員函式中一樣

struct C
{
    void bar();
 
    void foo(this C c)
    {
        auto x = this; // error: no this
        bar();         // error: no implicit this->
        c.bar();       // ok
    }
};

指向顯式物件成員函式的指標是普通的函式指標,而不是成員指標

struct Y 
{
    int f(int, int) const&;
    int g(this Y const&, int, int);
};
 
auto pf = &Y::f;
pf(y, 1, 2);              // error: pointers to member functions are not callable
(y.*pf)(1, 2);            // ok
std::invoke(pf, y, 1, 2); // ok
 
auto pg = &Y::g;
pg(y, 3, 4);              // ok
(y.*pg)(3, 4);            // error: “pg” is not a pointer to member function
std::invoke(pg, y, 3, 4); // ok
(C++23 起)

[編輯] 特殊成員函式

有些成員函式是特殊的:在某些情況下,即使使用者沒有定義它們,編譯器也會定義它們。它們是:

(C++11 起)
(C++11 起)

特殊成員函式以及比較運算子(C++20 起)是唯一可以被預設的函式,即使用 = default 而不是函式體來定義(詳見其頁面)。

[編輯] 注意

功能測試宏 標準 特性
__cpp_ref_qualifiers 200710L (C++11) ref-限定符
__cpp_explicit_this_parameter 202110L (C++23) 顯式物件引數推導 this

[編輯] 示例

#include <exception>
#include <iostream>
#include <string>
#include <utility>
 
struct S
{
    int data;
 
    // simple converting constructor (declaration)
    S(int val);
 
    // simple explicit constructor (declaration)
    explicit S(std::string str);
 
    // const member function (definition)
    virtual int getData() const { return data; }
};
 
// definition of the constructor
S::S(int val) : data(val)
{
    std::cout << "ctor1 called, data = " << data << '\n';
}
 
// this constructor has a catch clause
S::S(std::string str) try : data(std::stoi(str))
{
    std::cout << "ctor2 called, data = " << data << '\n';
}
catch(const std::exception&)
{
    std::cout << "ctor2 failed, string was '" << str << "'\n";
    throw; // ctor's catch clause should always rethrow
}
 
struct D : S
{
    int data2;
    // constructor with a default argument
    D(int v1, int v2 = 11) : S(v1), data2(v2) {}
 
    // virtual member function
    int getData() const override { return data * data2; }
 
    // lvalue-only assignment operator
    D& operator=(D other) &
    {
        std::swap(other.data, data);
        std::swap(other.data2, data2);
        return *this;
    }
};
 
int main()
{
    D d1 = 1;
    S s2("2");
 
    try
    {
        S s3("not a number");
    }
    catch(const std::exception&) {}
 
    std::cout << s2.getData() << '\n';
 
    D d2(3, 4);
    d2 = d1;   // OK: assignment to lvalue
//  D(5) = d1; // ERROR: no suitable overload of operator=
}

輸出

ctor1 called, data = 1
ctor2 called, data = 2
ctor2 failed, string was 'not a number'
2
ctor1 called, data = 3

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 194 C++98 非靜態成員函式是否
可以與外圍類同名
添加了顯式命名限制

[編輯] 另請參見