命名空间
变体
操作

非静态成员函数

来自 cppreference.com
< cpp‎ | language
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 规范符
动态异常规范 (直到 C++17*)
noexcept 规范符 (C++11)
异常
命名空间
类型
规范符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间规范符
初始化
 
 

非静态成员函数是在类的成员说明中声明的函数,没有 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 基类的非类型非静态成员的 标识符表达式 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 基类的静态成员、枚举器或嵌套类型的非限定标识符将被转换为相应的限定标识符

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
}

[edit] 带 cv 限定符的成员函数

隐式对象成员函数可以声明为带 cv 限定符 序列(constvolatileconstvolatile 的组合),此序列出现在 函数声明 中的参数列表之后。具有不同 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 起)

[edit] 虚函数和纯虚函数

非静态成员函数可以声明为虚函数纯虚函数。有关详细信息,请参见 虚函数抽象类

显式对象成员函数

对于没有使用 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&
}

这使得能够对常量和非常量成员函数进行重复数据消除,有关示例,请参见 数组下标运算符

此外,显式对象参数推断为派生类型,这简化了 CRTP

// a CRTP trait
struct add_postfix_increment
{
    template<typename Self>
    auto operator++(this Self&& self, int)
    {
        auto tmp = self; // Self deduces to "some_type"
        ++self;
        return tmp;
    }
};
 
struct some_type : add_postfix_increment
{
    some_type& operator++() { ... }
};

在显式对象成员函数体内部,不能使用 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 起)

[edit] 特殊成员函数

一些成员函数是特殊的:在某些情况下,即使没有由用户定义,它们也会由编译器定义。他们是

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

特殊成员函数 以及 比较运算符(自 C++20 起) 是唯一可以默认的函数,即使用 = default 而不是函数体来定义(有关详细信息,请参阅其页面)。

[edit] 注释

功能测试宏 Std 功能
__cpp_ref_qualifiers 200710L (C++11) 引用限定符
__cpp_explicit_this_parameter 202110L (C++23) 显式对象参数 (推断 this)

[edit] 示例

#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

[edit] 缺陷报告

以下行为改变缺陷报告已追溯应用于先前发布的 C++ 标准。

DR 应用于 已发布的行为 正确行为
CWG 194 C++98 模棱两可的是,非静态成员函数
是否可以与封闭类名相同
添加了明确的命名限制

[edit] 另请参阅