非静态成员函数

来自cppreference.com
< cpp‎ | language


 
 
 
 

非静态成员函数是在类的成员说明中不带 staticfriend 说明符声明的函数。(这些关键词的效果见静态成员函数友元声明

class S {int mf1();// 非静态成员函数声明void mf2()volatile, mf3()&&;// 可以有 cv 限定符或引用限定符// 上面的声明与下面分开的两个声明等价:// void mf2() volatile;// void mf3() &&;   int mf4()const{return data;}// 可以内联定义virtualvoid mf5() final;// 可以是虚函数,可以使用 final/override S(): data(12){}// 构造函数也是成员函数int data;};   int S::mf1(){return7;}// 不内联定义就必须在命名空间定义

构造函数析构函数转换函数的声明使用的是特殊的语法。本页描述的规则可能不适用于这些函数。细节可以参考它们的相应页面。

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

(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;// 变换为 (*this).n = 1;}   int main(){ S s1, s2; s1.f();// 更改 s1.n}

X 的非静态成员函数体内,任何解析到 XX 的某个基类的静态成员、枚举项或嵌套类型的无限定标识都会被变换为对应的有限定标识。

struct S {staticint n;void f();};   void S::f(){ n =1;// 变换为 S::n = 1;}   int main(){ S s1, s2; s1.f();// 更改 S::n}

[编辑]有 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 成员函数int operator[](int idx)const{// this 指针具有类型 const Array*return data[idx];// 变换为 (*this).data[idx];}   // 非 const 成员函数int& operator[](int idx){// this 指针具有类型 Array*return data[idx];// 变换为 (*this).data[idx]}};   int main(){ Array a(10); a[1]=1;// OK: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<<"左值\n";}void f()&&{std::cout<<"右值\n";}};   int main(){ S s; s.f();// 打印“左值” std::move(s).f();// 打印“右值” S().f();// 打印“右值”}

注意:与 cv 限定性不同,引用限定性不改变 this 指针的性质:即使在右值引用限定的函数中,*this 仍是左值表达式。

(C++11 起)

[编辑]虚函数和纯虚函数

非静态成员函数可以声明为 或纯虚 函数。细节见虚函数抽象类

显式对象成员函数

对于声明不带有 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();// OK}};

指向有显式对象形参的成员函数的指针是普通的函数指针,而不是成员的指针:

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);// OKstd::invoke(pf, y, 1, 2);// OK   auto pg =&Y::g; pg(y, 3, 4);// OK(y.*pg)(3, 4);// 错误:pg 不是指向成员函数的指针std::invoke(pg, y, 3, 4);// OK
(C++23 起)

[编辑]特殊成员函数

一些成员函数是特殊 的:在某些环境下,即使用户不定义,编译器也会定义它们。它们是:

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

特殊成员函数以及比较运算符(C++20 起)是仅有的能被预置 的函数,即使用 =default 替代函数体进行定义(细节见其相应页面)。

[编辑]注解

功能特性测试宏 标准功能特性
__cpp_ref_qualifiers200710L(C++11)引用限定符
__cpp_explicit_this_parameter202110L(C++23)显式对象形参推导 this

[编辑]示例

#include <exception>#include <iostream>#include <string>#include <utility>   struct S {int data;   // 简单的转换构造函数(声明) S(int val);   // 简单的显式构造函数(声明)explicit S(std::string str);   // const 成员函数(定义)virtualint getData()const{return data;}   };   // 构造函数的定义 S::S(int val): data(val){std::cout<<"调用构造函数1,data = "<< data <<'\n';}   // 此构造函数拥有 catch 子句 S::S(std::string str)try: data(std::stoi(str)){std::cout<<"调用构造函数2,data = "<< data <<'\n';}catch(conststd::exception&){std::cout<<"构造函数2失败,字符串为 '"<< 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("不是数字");}catch(conststd::exception&){}   std::cout<< s2.getData()<<'\n';   D d2(3, 4); d2 = d1;// OK:赋值给左值// D(5) = d1; // 错误:没有适合的 operator= 重载}

输出:

调用构造函数1,data = 1 调用构造函数2,data = 2 构造函数2失败,字符串为 '不是数字' 2 调用构造函数1,data = 3

[编辑]缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 194 C++98 (非静态)成员函数的名字可以与所在类的名字相同 添加命名限制

[编辑]参阅

close