一、重载的匹配
在前面的函数重载中其实大家明白了一个最根本的原则就是要保证最佳匹配的原则。就如同按照画像寻找一个人一样,先找特征一模一样的,再找类似点最多的,然后依次递减。从概率上分析,特征越是匹配,基本表明其可能是一个人的可能性要高很多。
函数重载的匹配相对于人来说,特征要相对简单很多。但对于初学者或者说不熟悉的开发者来说,仍然是一个比较难缠的问题。
二、具体的匹配方法
在前文的说明中,对具体匹配的优先级进行了说明即: 精确匹配;提升转换;标准转换;用户定义转换;可变参数匹配。其实举一个简单例子大家就明白了:
//伪代码
void test(int);
void test(double);
//调用
int a;char b;double c;
test(a);
test(b);
test(c);
在上面的代码中,a和c属于精确匹配,b属于提供转换。如果把test的double重载注释,c会调用test(int),这就属于标准转换了。当然,如果有多个同一等级的转换存在,编译器就无法分辨出应该调用哪个,会报出一个“ambiguous overload”错误。
下面就针对函数重载的具体过程进行一下分析说明:
- 先确定作用域。需要把整个作用域内的重名的函数查找并确定下来,即局部优于全局;导入同名函数需要通过包空间控制
- 确定是否有重载可能。需要判断的是函数名称(签名)相同;参数个数与调用的实参的一致性确定;在同一作用域内或可被查找
- 参数数量(含默认值处理,要明确有默认值与无默认值重载引起的冲突)处理及参数类型的转换处理,即支持上述匹配的优先级的实参与形参转换
- 处理友元和ADL(避免隐式的ADL),并确保其在同一命令空间内;注意,普通函数的匹配优于模板函数
- 处理模板的问题特别是SFINAE等机制引起的重载解析无效,即只有成功推导的模板参数参与重载
- 标准转换最高优先级为支持精确类型匹配和修饰符的转换(cv),其优先等级判断原则为:左传到右值;数据到指针;函数到指针以及算术转换。
- 提升转换要高于标准转换,即小到大高转换要高于非同系类型的转换。比如float到double要高于int到double的转换。即其在重载中优先级会高
- 用户自定义转换的需要处理好自定义转换与隐式转换化的歧义;不允许进行基础类型的转换和双向隐式转换(有可能引发循环定义);模板中的自定义要防止引起推导的冲突异常
- 最后处理变参情况,可以认为变参是一种保底方式
- 依据上面的作用域、签名、具体的处理(如模板的推导)、参数匹配优先级最终确定最佳的匹配函数并调用
在掌握了上述的具体流程逻辑后,就可以分析和解决在重载中为何遇到了问题。并有的放矢的解决实际的问题。
三、举例说明
下面看一个比较全面的重载函数的例子:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <type_traits>
#include <concepts>
// 参数不同的重载
void print() {
std::cout << "call print: args 0!" << std::endl;
}
void print(int value) {
std::cout << "call print:args 1 " << value << std::endl;
}
void print(int a, int b) {
std::cout << "call print: args 2" << a << ", " << b << std::endl;
}
// 参数不同类型的重载
void print(const char* value) {
std::cout << "call print:args const char*: " << value << std::endl;
}
void print(const std::string& value) {
std::cout << "call print:args const string&: " << value << std::endl;
}
void print(double value) {
std::cout << "call print:args type double: " << value << std::endl;
}
// cv及引用限定符重载
class CVDemo {
private:
std::string str_;
public:
CVDemo(const std::string& n) : str_(n) {}
//const重载
const std::string& getStr() const {
std::cout << "getStr() const called ."<< std::endl;
return str_;
}
std::string& getStr() {
std::cout << "getStr() non-const called" << std::endl;
return str_;
}
//引用限定符重载
void control() & {
std::cout << "call control & => lvalue" << std::endl;
}
void control() && {
std::cout << "call control && =>rvalue" << std::endl;
}
void control() const & {
std::cout << "call control const & => const lvalue" << std::endl;
}
};
// 函数模板重载
template<typename T>
void display(T value) {
std::cout << "call display(T) ,args : " << value << std::endl;
}
template<typename T>
void display(T* ptr) {
std::cout << "call display(T*): pointer args : " << ptr<<std::endl;
if (ptr) {
std::cout << ", value = " << *ptr<<std::endl;
}
}
template<>
void display<std::string>(std::string value) {
std::cout << "call display<string>: specialization args:" << value << std::endl;
}
// 变参模板重载
template<typename... Args>
void PrintData(Args&&... args) {
std::cout << "PrintData(Args...): args num: " << sizeof...(Args) << std::endl;
((std::cout << args << " "), ...);
std::cout << std::endl;
}
void PrintData(const char* msg) {
std::cout << "PrintData(const char*): C-string:" << msg << std::endl;
}
// 运算符重载
class Complex {
private:
double real_=0.0;
double imag_ =0.0;
public:
Complex(double r = 0, double i = 0) : real_(r), imag_(i) {}
// 成员运算符重载
Complex operator+(const Complex& other) const {
return Complex(real_ + other.real_, imag_ + other.imag_);
}
Complex operator-() const {
return Complex(-real_, -imag_);
}
// 前缀和后缀++
Complex& operator++() {
++real_;
return *this;
}
Complex operator++(int) {
Complex temp = *this;
++real_;
return temp;
}
// 转换运算符
explicit operator double() const {
return real_;
}
friend Complex operator+(double value, const Complex& c) ;
friend std::ostream& operator<<(std::ostream& os, const Complex& c);
};
// 非成员运算符重载
std::ostream& operator<<(std::ostream& os, const Complex& c) {
os << c.real_ << " + " << c.imag_ << "i";
return os;
}
Complex operator+(double value, const Complex& c) {
return Complex(value + c.real_, c.imag_);
}
// 继承中的重载
class Base {
public:
//虚函数重载
virtual void run(int d) {
std::cout << "call Base::run(int): " << d << std::endl;
}
virtual void run(double d) {
std::cout << "call Base::run(double): " << d << std::endl;
}
void genFunc(int d) {
std::cout << "Base::genFunc(int): " << d << std::endl;
}
virtual ~Base() = default;
};
class Derived : public Base {
public:
// 隐藏基类重载
void run(int x) override {
std::cout << "Derived::run(int): " << x << std::endl;
}
// 引入基类重载的解决方案
using Base::run;
void genFunc(const std::string& s) {
std::cout << "Derived::genFunc(string): " << s << std::endl;
}
};
//概念的重载 (C++20)
template<typename T>
concept Arith = std::is_Arith_v<T>;
template<typename T>
concept StrSimilar = requires(T t) {
{ std::to_string(t) } -> std::convertible_to<std::string>;
};
template<Arith T>
void conceptsDowith(T value) {
std::cout << "call concepts (Arith): " << value << std::endl;
}
template<StrSimilar T>
void conceptsDowith(T value) {
std::cout << "call concepts(StrSimilar): " << std::to_string(value) << std::endl;
}
//完美转发重载
template<typename T>
void dowith(T&& value) {
std::cout << " call dowith ,value : " << value << std::endl;
}
template<typename T>
void forwardDemo(T&& value) {
std::cout << "call forwardDemo(T&&)" << std::endl;
dowith(std::forward<T>(value));
}
void forwardDemo(const std::string& value) {
std::cout << "call forwardDemo(const string&): " << value << std::endl;
}
void orderDemo(int a, int b) {
std::cout << "call orderDemo(int, int): " << a << ", " << b << std::endl;
}
void orderDemo(double a, double b) {
std::cout << "call orderDemo(double, double): " << a << ", " << b << std::endl;
}
//此处会产生ambiguous的问题
void orderDemo(int a, double b) {
std::cout << "call orderDemo(int, double): " << a << ", " << b << std::endl;
}
void baseTest() {
print();
print(66);
print(7.10);
print(3, 7);
print("test");
print(std::string(" demo"));
}
void constRefTest() {
CVDemo cvd1("ovTest");
const CVDemo cvd2("constOVTest");
// const 重载
cvd1.getStr() = "111";
cvd2.getStr();
// 引用限定符重载
cvd1.control(); // lvalue
CVDemo("ok").control(); // rvalue
cvd2.control(); // const lvalue
}
void templateTest() {
int x = 2;
display(x);
display(&x); // display(T*)
display(std::string("template")); // 特化
PrintData("msg test ");
PrintData(1, 2.5, "three"); // PrintData(Args...)
}
void opTest() {
Complex c1(10, 3), c2(63, 7);
Complex c3 = c1 + c2;
Complex c4 = -c1;
Complex c5 = 8.0 + c1;
std::cout << "c1 = " << c1 << std::endl;
std::cout << "c2 = " << c2 << std::endl;
std::cout << "c1 + c2 = " << c3 << std::endl;
std::cout << "-c1 = " << c4 << std::endl;
std::cout << "8.0 + c1 = " << c5 << std::endl;
++c1;
std::cout << "++c1 = " << c1 << std::endl;
Complex c6 = c2++;
std::cout << "c2++ = " << c6 << ", c2 = " << c2 << std::endl;
}
void inheritTest() {
Derived d;
Base* bp = &d;
d.run(10);
d.run(3.14);
bp->run(10); //多态
bp->run(6.60);
}
void conceptsTest() {
conceptsDowith(33);
conceptsDowith(2.23);
conceptsDowith(90L);
std::string str = "keep";
forwardDemo(str);
forwardDemo(std::move(str)); // (T&&)
forwardDemo("abc"); // const string&
}
void orderTest() {
orderDemo(1, 2); // int, int
orderDemo(1, 2.5); // int, double
orderDemo(1.5, 2); // double, double
// orderDemo(1.5, 2.5); // double, double
}
int main() {
std::cout << "启动重载测试......" << std::endl;
baseTest();
constRefTest();
templateTest();
opTest();
inheritTest();
conceptsTest();
orderTest();
return 0;
}
代码并不难,认真与上面的分析流程比对即可明白相关的技术点。
四、总结
C++之所以被很多的开发者认为难,有很大一部分在于细节过于繁杂,让很多老程序员都防不胜防。特别是突然进入一个不太熟悉的场景下,出现一些基础的技术性问题是在所难免的。但是只要认真的分析相关技术点,找到基础的不足之处加以补齐,这样的问题其实并不是什么问题。
但实际问题往往是,不少的开发者在经过一段时间的工程实践后,已经不再愿意踏下心来学习基础的知识点,忽视或轻视基础知识点的扩展,这就为开发埋下了一些隐藏的雷。

1486

被折叠的 条评论
为什么被折叠?



