Namespaces
Variants

Non-static member functions

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

非静态成员函数是在类的 成员声明 中未使用 static friend 说明符声明的函数 (关于这些关键字的作用,请参阅 静态成员函数 友元声明 )。

class S
{
    int mf1(); // 非静态成员函数声明
    void mf2() volatile, mf3() &&; // 可包含cv限定符和/或引用限定符
        // 上述声明等价于两个独立声明:
        // void mf2() volatile;
        // void mf3() &&;
    int mf4() const { return data; } // 可内联定义
    virtual void mf5() final; // 可为虚函数,可使用final/override
    S() : data(12) {} // 构造函数同样属于成员函数
    int data;
};
int S::mf1() { return 7; } // 若非内联定义,需在命名空间作用域定义

构造函数 析构函数 转换函数 使用特殊的语法进行声明。本页描述的规则可能不适用于这些函数。详情请参阅各自的页面。

显式对象成员函数 是带有 显式对象形参 的非静态成员函数。

(since C++23)

一个 隐式对象成员函数 是指没有显式对象参数的非静态成员函数(在C++23之前,这是非静态成员函数的唯一形式,因此在文献中直接称为"非静态成员函数")。

目录

说明

允许任何 函数声明 ,同时包含仅适用于非静态成员函数的额外语法元素: 纯说明符 、cv限定符 、引用限定符、 final override 说明符 (C++11 起) ,以及 成员初始化列表

X 的非静态成员函数可被调用

1) 对于使用类成员访问运算符的 X 类型对象
2) 对于派生自 X 的类的对象
3) 直接在 X 的成员函数体内
4) 直接从派生自 X 的类的成员函数体内部

在非 X 类型或非 X 派生类型的对象上调用类 X 的非静态成员函数会导致未定义行为。

X 的非静态成员函数体内,任何解析为 X 或其基类的非类型非静态成员的 标识表达式 e (例如标识符),都会被转换为成员访问表达式 ( * this ) . e (除非它已是成员访问表达式的一部分)。这一转换不会发生在模板定义上下文中,因此可能需要显式使用 this - > 前缀使名称成为 依赖名

struct S
{
    int n;
    void f();
};
void S::f()
{
    n = 1; // 转换为 (*this).n = 1;
}
int main()
{
    S s1, s2;
    s1.f(); // 修改 s1.n
}

X 的非静态成员函数体内,任何解析为 X 或其基类的静态成员、枚举项或嵌套类型的非限定标识符,都会被转换为对应的限定标识符:

struct S
{
    static int n;
    void f();
};
void S::f()
{
    n = 1; // 转换为 S::n = 1;
}
int main()
{
    S s1, s2;
    s1.f(); // 修改 S::n
}

带 cv 限定符的成员函数

隐式对象成员函数可以通过 cv限定符 序列( const volatile ,或两者的组合)进行声明,该序列出现在 函数声明 的参数列表之后。具有不同cv限定符序列(或无序列)的函数具有不同的类型,因此可以相互重载。

在带有 cv 限定符序列的函数体内, * this 会被相应 cv 限定,例如在带有 const 限定符的成员函数中,通常只能调用其他带有 const 限定符的成员函数。若通过 const_cast 或未涉及 this 的访问路径,仍可调用无 const 限定符的成员函数。

#include <vector>
struct Array
{
    std::vector<int> data;
    Array(int sz) : data(sz) {}
    // 常量成员函数
    int operator[](int idx) const
    {                     // this 指针的类型为 const Array*
        return data[idx]; // 转换为 (*this).data[idx];
    }
    // 非常量成员函数
    int& operator[](int idx)
    {                     // this 指针的类型为 Array*
        return data[idx]; // 转换为 (*this).data[idx]
    }
};
int main()
{
    Array a(10);
    a[1] = 1;  // 正确:a[1] 的类型是 int&
    const Array ca(10);
    ca[1] = 2; // 错误:ca[1] 的类型是 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 起)

虚函数与纯虚函数

非静态成员函数可被声明为 virtual pure virtual 。详见 虚函数 抽象类 说明。

显式对象成员函数

对于未用 cv 限定符或引用限定符声明的非静态非虚成员函数,其第一个参数(若非 函数形参包 )可以是 显式对象形参 (以前缀关键字 this 表示):

struct X
{
    void foo(this X const& self, int i); // 等同于 void foo(int i) const &;
//  void foo(int i) const &; // 错误:已声明
    void bar(this X self, int i); // 按值传递对象:生成“*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; // 错误:不存在 this
        bar();         // 错误:无隐式 this->
        c.bar();       // 正确
    }
};

指向显式对象成员函数的指针是普通函数指针,而非成员指针:

struct Y 
{
    int f(int, int) const&;
    int g(this Y const&, int, int);
};
auto pf = &Y::f;
pf(y, 1, 2);              // 错误:指向成员函数的指针不可调用
(y.*pf)(1, 2);            // 正确
std::invoke(pf, y, 1, 2); // 正确
auto pg = &Y::g;
pg(y, 3, 4);              // 正确
(y.*pg)(3, 4);            // 错误:“pg”不是指向成员函数的指针
std::invoke(pg, y, 3, 4); // 正确
(C++23 起)

特殊成员函数

某些成员函数是 特殊 的:在特定情况下,即使使用者未定义,它们也会由编译器自动生成。这些函数包括:

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

特殊成员函数 以及 比较运算符 (自C++20起) 是唯一可被 缺省定义 的函数,即使用 = default 代替函数体进行定义(详见各函数说明页)。

注释

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

示例

#include <exception>
#include <iostream>
#include <string>
#include <utility>
struct S
{
    int data;
    // 简单转换构造函数(声明)
    S(int val);
    // 简单显式构造函数(声明)
    explicit S(std::string str);
    // const 成员函数(定义)
    virtual int getData() const { return data; }
};
// 构造函数的定义
S::S(int val) : data(val)
{
    std::cout << "ctor1 called, data = " << data << '\n';
}
// 此构造函数包含 catch 子句
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; // 构造函数的 catch 子句应始终重新抛出
}
struct D : S
{
    int data2;
    // 带默认参数的构造函数
    D(int v1, int v2 = 11) : S(v1), data2(v2) {}
    // 虚成员函数
    int getData() const override { return data * data2; }
    // 仅限左值的赋值运算符
    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;   // 正确:对左值进行赋值
//  D(5) = d1; // 错误:没有合适的 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 未明确非静态成员函数
是否可与所在类同名
已添加显式命名限制

参见