代码存储位置:shanchuann /Modern_CPP

声明

在函数声明中,当我们想给函数形参声明为默认实参时,我们需要确保在当前作用域下 该形参后的形参也是_默认形参_ 。

1
2
3
4
5
6
7
8
9
10
//在函数声明中,形参若想声明默认实参,则应当确保当前或同一作用域下提前给其后面的形参声明了默认实参
void f(int, int, int = 10);
void f(int, int = 20, int);
void f(int = 30, int, int);
void f(int a, int b, int c) {
cout << a << " " << b << " " << c << endl;
}
int main() {
f();
}

当我们将void f(int, int = 20, int); 移动至第一行时,会显示以下ERROR

1
2
3
void f(int, int = 20, int);
void f(int, int, int = 10);
void f(int = 30, int, int);
1
2
3
4
5
1>D:\cpp language\现代C++\默认实参.cpp(6,27): error C2548: “f”: 缺少形参 3 的默认实参
1>D:\cpp language\现代C++\默认实参.cpp(7,24): error C2572: “f”: 重定义默认参数 : 参数 1
1> D:\cpp language\现代C++\默认实参.cpp(6,6):
1> 参见“f”的声明
1>D:\cpp language\现代C++\默认实参.cpp(8,27): error C2548: “f”: 缺少形参 2 的默认实参

第二个形参声明为默认实参是没有问题的,但前提是第三个参数也被声明为了默认实参。

除非该形参是从某个形参包展开得到或是函数形参包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 除非该形参是从某个形参包展开得到或是函数形参包
template<class...T>
struct X {
void f(int n = 0, T...args) { cout << n << endl; };
};

template<class...Args>
void f_(int n = 6, Args...args) {

}

int main() {
X<>().f();
X<int>().f(1, 2);
}

类作用域

对于非模板类的 成员函数类外的初始化 中允许出现默认实参,并与类内声明时提供的默认实参组合 。如果类外的默认实参会使成员函数变成默认构造函数或复制 /移动构造函数 /赋值运算符,那么程序非良构(指程序存在语法错误或可诊断的语义错误)。

合法的默认参数组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyClass {
public:
void normalFunction(int a, int b = 10); // 类内声明:b有默认值

// 特殊成员函数不能有默认参数(除了拷贝构造等少数例外)
MyClass(int x = 0); // 这是构造函数,允许有默认参数
};

// 类外定义:可以添加或修改默认参数
void MyClass::normalFunction(int a = 5, int b) { // 添加a的默认值
cout << "a = " << a << ", b = " << b << endl;
}

// 构造函数的类外定义(如果分开定义)
MyClass::MyClass(int x) {}

int main() {
MyClass obj;
obj.normalFunction();
obj.normalFunction(20);
obj.normalFunction(30, 40);
}

组合规则:

  • 类内:normalFunction(int a, int b = 10)
  • 类外:normalFunction(int a = 5, int b)
  • 最终签名:normalFunction(int a = 5, int b = 10)

输出

1
2
3
a = 5, b = 10
a = 20, b = 10
a = 30, b = 40

非良构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Problematic {
public:
Problematic(int x); // 声明为普通构造函数

// 拷贝构造函数声明
Problematic(const Problematic& other);
};

// 非良构:试图通过默认参数使普通构造函数变成默认构造函数
Problematic::Problematic(int x = 0) { // 错误!会使构造函数变成默认构造函数
// 实现
}

// 非良构:拷贝构造函数添加默认参数
Problematic::Problematic(const Problematic& other = Problematic(0)) { // 错误!
// 实现
}

对于类模板的成员函数,所有默认实参必须在成员函数的初始声明处提供。

1
2
3
4
5
6
7
8
class C {
void f(int a = 10);
void g(int a, int b = 20);
C(int a); //并非默认构造函数
};
void C::f(int a = 20) {}
void C::g(int a = 10, int b) {}
C::C(int a = 20) {}
1
2
3
1>D:\cpp language\现代C++\默认实参.cpp(8,19): error C2572: “C::f”: 重定义默认参数 : 参数 1
1> D:\cpp language\现代C++\默认实参.cpp(4,10):
1> 参见“C::f”的声明

C::f 重定义了默认实参

C::C 使得构造函数变为了默认构造函数

虚函数与默认实参

虚函数的覆盖函数不会从基类定义获得默认实参,而在进行虚函数调用时,默认实参根据对象的静态类型确定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Base {
virtual void f(int a = 10) { cout << "Base::f, a = " << a << endl; }
};
struct Derived : Base {
void f(int a) override { cout << "Derived::f, a = " << a << endl; }
};
int main() {
Derived d;
Base* b = &d;
//d.f(); // 错误,Derived::f没有默认实参
b->f(); // 正确,调用Base::f,输出 "Base::f, a = 10"

//std::unique_ptr<Base>ptr(new Deried);
//ptr->f();

return 0;
}

输出:Derived::f, a = 10

静态类型:对程序进行编译时分析得到的表达式的类型 被称为表达式的静态类型,程序执行时静态类型不会更改。

动态类型:如果某个 泛左值表达式(左值和将亡值的合称) 指代某个多态对象,那么他的最终派生对象的类型被称为他的动态类型。

1
2
3
4
5
6
7
//给定
struct B { virtual ~B() {} };
struct D : B {};
D d; //最终派生对象
B* pb = &d; // 向上转型

//pb的静态类型是B*,动态类型是D*

由此可见,静态类型为父类,所以最后的打印结果为"Base::f, a = 10 "

不求值语境

默认实参能在_不求值语境( typeid 、 sizeof 、 noexcept 和 decltype)_ 中使用局部变量

1
2
3
4
5
6
7
8
int main() {
int f = 0;
void f2(int n = sizeof f);
f2();
}
void f2(int n) {
cout << n << endl;
}

在Visual Studio 2022 内置的 MSVC编译器中会报错

1
2
3
4
5
6
7
8
1>D:\cpp language\现代C++\默认实参.cpp(6,28): error C2587: “f”: 非法将局部变量作为默认参数
1> D:\cpp language\现代C++\默认实参.cpp(5,6):
1> 参见“f”的声明
1>D:\cpp language\现代C++\默认实参.cpp(7,5): error C2660: “f2”: 函数不接受 0 个参数
1> D:\cpp language\现代C++\默认实参.cpp(6,10):
1> 参见“f2”的声明
1> D:\cpp language\现代C++\默认实参.cpp(7,5):
1> 尝试匹配参数列表“()”时

但采用G++编译则是正常打印结果,这只是编译器差异

image.png

类成员

默认实参中不能使用非静态类成员(即使他们不被求值) ,除非用于构成成员指针或在成员访问表达式中使用。

1
2
3
4
5
6
7
8
9
10
struct X {
int n = 10;
static const int sn = 20;
void f(int n = sizeof + n) { cout << n << endl; }
void f_(int n = sn) { cout << n << endl; }
};
int main() {
X().f();
X().f_();
}
1
2
3
4
1>D:\cpp language\现代C++\默认实参.cpp(7,16): error C2648: “X::n”: 将成员作为默认参数使用要求静态成员
1> D:\cpp language\现代C++\默认实参.cpp(5,9):
1> 参见“X::n”的声明
1>D:\cpp language\现代C++\默认实参.cpp(7,16): error C2355: “this”: 只能在非静态成员函数或非静态数据成员初始值设定项的内部引用

所以即使是不求值的语境同样不行。