您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

深入理解C++方法重载、内联与高级用法

时间:2023-11-23 12:16:00  来源:微信公众号  作者:coding日记

深入理解C++方法重载、内联与高级用法

方法重载

你可能已经注意到,你可以在一个类中写多个构造函数,所有这些构造函数都有相同的名字。这些构造函数只在参数的数量和/或类型上有所不同。你可以对C++中的任何方法或函数做同样的事情。具体来说,你可以通过为具有不同数量和/或类型的参数的多个函数使用同一个名称来重载一个函数或方法。例如,在SpreadsheetCell类中,你可以将setString()和setValue()都重命名为set()。类定义现在看起来像这样:

export class SpreadsheetCell {
public:
    void set(double value);
    void set(std::string_view value);
    // 省略了一些内容以保持简洁
};

set()方法的实现保持不变。当你编写代码调用set()时,编译器会根据你传递的参数来确定调用哪个实例:如果你传递一个string_view,编译器会调用string_view实例;如果你传递一个double,编译器会调用double实例。这被称为重载解析。

你可能会试图对getValue()和getString()做同样的事情:将它们都重命名为get()。然而,这样做是不行的。C++不允许你仅基于方法的返回类型来重载一个方法名,因为在许多情况下,编译器无法确定你试图调用的是哪个方法实例。例如,如果方法的返回值没有被捕获在任何地方,编译器就没有办法知道你试图调用的是哪个方法实例。

基于const的重载

你可以基于const来重载一个方法。也就是说,你可以写两个具有相同名称和相同参数的方法,一个声明为const,另一个则不是。如果你有一个const对象,编译器会调用const方法;如果你有一个非const对象,它会调用非const重载。通常,const重载和非const重载的实现是相同的。为了避免代码重复,你可以使用Scott Meyer的const_cast()模式。

例如,Spreadsheet类有一个名为getCellAt()的方法,返回对非const SpreadsheetCell的引用。你可以添加一个const重载,返回对const SpreadsheetCell的引用,如下所示:

export class Spreadsheet {
public:
    SpreadsheetCell& getCellAt(size_t x, size_t y);
    const SpreadsheetCell& getCellAt(size_t x, size_t y) const;
    // 代码省

Scott Meyer的const_cast()模式将const重载实现为你通常会做的那样,并通过适当的转换将非const重载的调用转发给const重载,如下所示:

const SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y) const {
    verifyCoordinate(x, y);
    return m_cells[x][y];
}

SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y) {
    return const_cast<SpreadsheetCell&>(as_const(*this).getCellAt(x, y));
}

基本上,你首先使用std::as_const()(定义在<utility>中)将*this(一个Spreadsheet&)转换为const Spreadsheet&。接下来,你调用getCellAt()的const重载,它返回一个const SpreadsheetCell&。然后你用const_cast()将这个转换为非const SpreadsheetCell&。

有了这两个getCellAt()的重载,你现在可以在const和非const Spreadsheet对象上调用getCellAt():

Spreadsheet sheet1 { 5, 6 };
SpreadsheetCell& cell1 { sheet1.getCellAt(1, 1) };
const Spreadsheet sheet2 { 5, 6 };
const SpreadsheetCell& cell2 { sheet2.getCellAt(1, 1) };

在这种情况下,const重载的getCellAt()并没有做太多的事情,所以你通过使用const_cast()模式并没有赢得太多。然而,想象一下,如果const重载的getCellAt()做了更多的工作;那么将非const重载转发给const重载可以避免重复那些代码。

显式删除重载

重载的方法可以被显式删除,这使你能够禁止使用特定参数调用某个方法。例如,SpreadsheetCell类有一个setValue(double)方法,可以这样调用:

SpreadsheetCell cell;
cell.setValue(1.23);
cell.setValue(123);

对于第三行,编译器将整数值(123)转换为double,然后调用setValue(double)。如果由于某种原因,你不希望setValue()使用整数调用,你可以显式删除setValue()的整数重载:

export class SpreadsheetCell {
public:
    void setValue(double value);
    void setValue(int) = delete;
};

有了这个改变,尝试使用整数调用setValue()的操作将被编译器标记为错误。

Ref-Qualified方法

普通类方法可以在非临时和临时类实例上调用。假设你有以下类:

class TextHolder {
public:
    TextHolder(string text) : m_text { move(text) } {}
    const string& getText() const { return m_text; }
private:
    string m_text;
};

当然,毫无疑问,你可以在非临时实例的TextHolder上调用getText()方法。这里有一个例子:

TextHolder textHolder { "Hello world!" };
cout << textHolder.getText() << endl;

然而,getText()也可以在临时实例上调用:

cout << TextHolder{ "Hello world!" }.getText() << endl;
cout << move(textHolder).getText() << endl;

你可以通过添加所谓的ref-qualifier来明确指定可以在哪种类型的实例上调用某个方法,无论是临时的还是非临时的。如果一个方法只应该在非临时实例上调用,在方法头后加上&限定符。类似地,如果一个方法只应该在临时实例上调用,在方法头后加上&&限定符。

下面修改后的TextHolder类实现了带有&限定符的getText(),通过返回对m_text的引用。而带有&&限定符的getText()返回m_text的右值引用,这样m_text就可以从TextHolder中移动出来。如果你想从临时TextHolder实例中检索文本,这可能会更有效率。

class TextHolder {
public:
    TextHolder(string text) : m_text { move(text) } {}
    const string& getText() const & { return m_text; }
    string&& getText() && { return move(m_text); }
private:
    string m_text;
};

假设你有以下调用:

TextHolder textHolder { "Hello world!" };
cout << textHolder.getText() << endl;
cout << TextHolder{ "Hello world!" }.getText() << endl;
cout << move(textHolder).getText() << endl;

那么第一次调用getText()会调用带有&限定符的重载,而第二次和第三次调用则会调用带有&&限定符的重载。

内联方法

C++允许你建议调用一个方法(或函数)时,不应该在生成的代码中实际实现为调用一个单独的代码块。相反,编译器应该将方法的主体直接插入到调用该方法的代码中。这个过程被称为内联,希望这种行为的方法被称为内联方法。

你可以通过在方法定义中的名字前放置inline关键字来指定一个内联方法。例如,你可能想让SpreadsheetCell类的访问器方法成为内联的,这种情况下,你会这样定义它们:

inline double SpreadsheetCell::getValue() const {
    m_numaccesses++;
    return m_value;
}

inline std::string SpreadsheetCell::getString() const {
    m_numAccesses++;
    return doubleToString(m_value);
}

这向编译器提供了一个提示,用实际的方法体替换对getValue()和getString()的调用,而不是生成代码来进行函数调用。请注意,inline关键字只是一个提示给编译器。如果编译器认为这会影响性能,它可以忽略它。

有一个注意事项:内联方法(和函数)的定义必须在每个调用它们的源文件中都可用。如果你想一下,这是有道理的:如果编译器看不到方法定义,它怎么能代替方法的主体呢?因此,如果你编写内联方法,你应该将这些方法的定义放在类定义所在的同一个文件中。

注意,高级C++编译器不要求你将内联方法的定义放在类定义的同一个文件中。例如,Microsoft Visual C++支持链接时代码生成(LTCG),它会自动内联小的函数体,即使它们没有被声明为inline,即使它们没有定义在类定义的同一个文件中。GCC和Clang也有类似的功能。

在C++20模块之外,如果一个方法的定义直接放在类定义中,即使没有使用inline关键字,该方法也隐式地被标记为内联。使用C++20中从模块导出的类时,情况并非如此。如果你希望这些方法是内联的,你需要用inline关键字标记它们。这里有一个例子:

export class SpreadsheetCell {
public:
    inline double getValue() const {
        m_numAccesses++;
        return m_value;
    }
    inline std::string getString() const {
        m_numAccesses++;
        return doubleToString(m_value);
    }
    // 省略了一些内容以保持简洁
}

注意,如果你在调试器中单步执行一个被内联的函数调用,一些高级C++调试器会跳转到内联函数的实际源代码,给你造成了函数调用的假象,而实际上代码是内联的。许多C++程序员在不理解将一个方法标记为内联的后果时,就使用了内联方法语法。将一个方法或函数标记为内联只是给编译器一个提示。编译器只会内联最简单的方法和函数。如果你定义了一个编译器不想内联的内联方法,它会默默地忽略这个提示。现代编译器会在决定内联一个方法或函数之前,考虑诸如代码膨胀等指标,并且不会内联任何不划算的东西。

默认参数

在C++中,与方法重载类似的功能是默认参数。你可以在原型中为函数和方法参数指定默认值。如果用户为这些参数提供了参数,那么默认值将被忽略。如果用户省略了这些参数,将使用默认值。不过,有一个限制:你只能为从最右边的参数开始的连续参数列表提供默认值。否则,编译器将无法将缺失的参数与默认参数匹配。默认参数可用于函数、方法和构造函数。例如,你可以为Spreadsheet构造函数中的宽度和高度分配默认值,如下所示:

export class Spreadsheet {
public:
    Spreadsheet(size_t width = 100, size_t height = 100);
    // 省略了一些内容以保持简洁
};

Spreadsheet构造函数的实现保持不变。请注意,你只在方法声明中指定默认参数,而不是在定义中指定。现在,尽管只有一个非复制构造函数,你仍然可以使用零个、一个或两个参数调用Spreadsheet构造函数:

Spreadsheet s1;
Spreadsheet s2 { 5 };
Spreadsheet s3 { 5, 6 };

一个为所有参数提供默认值的构造函数可以作为默认构造函数。也就是说,你可以在不指定任何参数的情况下构造该类的对象。如果你尝试同时声明一个默认构造函数和一个为所有参数提供默认值的多参数构造函数,编译器会报错,因为如果你不指定任何参数,它不知道该调用哪个构造函数。



Tags:C++   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
C++常见避坑指南
C++ 从入门到放弃?本文主要总结了在C++开发或review过程中常见易出错点做了归纳总结,希望借此能增进大家对C++的了解,减少编程出错,提升工作效率,也可以作为C++开发的避坑攻略。...【详细内容】
2024-04-03  Search: C++  点击:(4)  评论:(0)  加入收藏
C++ 之父反驳白宫警告:自诞生第一天起,C++ 的目标就一直是提高安全性
整理 | 郑丽媛上个月,美国白宫国家网络主任办公室(ONCD)在一份主题为《回到基础构件:通往安全软件之路》的 19 页 PDF 报告中,呼吁开发人员停止使用容易出现内存安全漏洞的编程语...【详细内容】
2024-03-25  Search: C++  点击:(4)  评论:(0)  加入收藏
八个 C++ 开源项目,帮助初学者进阶成长
通过参与或阅读开源项目的源代码,你可以获得丰富的实践机会。实际的项目代码比简单的教程更具挑战性,可以帮助你深入理解 C++ 的各种概念和技术。1.ThreadPool一个简单的 C++1...【详细内容】
2024-03-22  Search: C++  点击:(21)  评论:(0)  加入收藏
C++多线程编程:解锁性能与并发的奥秘
今天我们将深入探讨C++中的多线程编程,揭示多线程如何解锁性能潜力,提高程序的并发性能。什么是多线程?在计算机科学中,多线程是指一个进程(程序的执行实例)中的多个线程同时执行...【详细内容】
2024-02-03  Search: C++  点击:(68)  评论:(0)  加入收藏
C++代码优化攻略
今天我们将深入探讨C++性能优化的世界。在当今软件开发的浪潮中,高性能的代码是必不可少的。无论是开发桌面应用、移动应用,还是嵌入式系统,性能都是关键。1. 选择合适的数据结...【详细内容】
2024-01-26  Search: C++  点击:(113)  评论:(0)  加入收藏
C++质数检测器的设计与实现​
质数,作为数学中的一个基本概念,一直以其独特的性质吸引着众多研究者和爱好者。质数是指大于1的自然数中,除了1和它本身以外不再有其他因数的数。在实际应用中,质数检测也扮演着...【详细内容】
2024-01-15  Search: C++  点击:(110)  评论:(0)  加入收藏
指针变量在C/C++中的内存占用
在编程领域,尤其是C和C++这类底层语言中,指针是一个核心概念,它允许程序直接操作内存地址。然而,关于指针本身在内存中占用的空间大小,却常常让初学者感到困惑。本文将深入探讨这...【详细内容】
2024-01-09  Search: C++  点击:(94)  评论:(0)  加入收藏
C++的面向对象编程:深入解析与理解
当我们谈论C++时,面向对象编程(OOP)是一个无法回避的话题。那么,C++的面向对象究竟是什么?为什么它如此重要?本文将从基本概念到实际应用,为您详细解析C++中的面向对象编程。一、面...【详细内容】
2024-01-03  Search: C++  点击:(95)  评论:(0)  加入收藏
有什么好用的C/C++源代码混淆工具?
开始使用ipaguard前言iOS加固保护是直接针对ios ipa二进制文件的保护技术,可以对iOS APP中的可执行文件进行深度混淆、加密。使用任何工具都无法逆向、破解还原源文件。对APP...【详细内容】
2023-12-29  Search: C++  点击:(117)  评论:(0)  加入收藏
C++中new与malloc:内存分配机制深度解析
本文旨在深入探讨C++中new和malloc两种内存分配机制的区别。通过对比它们在内存分配、初始化、错误处理、调用构造函数/析构函数、类型转换和使用便捷性等方面的不同,我们将...【详细内容】
2023-12-27  Search: C++  点击:(126)  评论:(0)  加入收藏
▌简易百科推荐
C++常见避坑指南
C++ 从入门到放弃?本文主要总结了在C++开发或review过程中常见易出错点做了归纳总结,希望借此能增进大家对C++的了解,减少编程出错,提升工作效率,也可以作为C++开发的避坑攻略。...【详细内容】
2024-04-03  腾讯技术工程    Tags:C++   点击:(4)  评论:(0)  加入收藏
C++ 之父反驳白宫警告:自诞生第一天起,C++ 的目标就一直是提高安全性
整理 | 郑丽媛上个月,美国白宫国家网络主任办公室(ONCD)在一份主题为《回到基础构件:通往安全软件之路》的 19 页 PDF 报告中,呼吁开发人员停止使用容易出现内存安全漏洞的编程语...【详细内容】
2024-03-25    CSDN  Tags:C++   点击:(4)  评论:(0)  加入收藏
八个 C++ 开源项目,帮助初学者进阶成长
通过参与或阅读开源项目的源代码,你可以获得丰富的实践机会。实际的项目代码比简单的教程更具挑战性,可以帮助你深入理解 C++ 的各种概念和技术。1.ThreadPool一个简单的 C++1...【详细内容】
2024-03-22  AI让生活更美好  微信公众号  Tags:C++   点击:(21)  评论:(0)  加入收藏
C# 中15个值得收藏的开源项目推荐
在开源的世界里,C# 编程语言也占有一席之地。这些开源项目涵盖了多个领域,从框架、库到工具,它们为C#开发者提供了丰富的资源和工具,帮助他们更高效地开发、测试和部署应用程序...【详细内容】
2024-03-20  程序员编程日记  微信公众号  Tags:C#   点击:(29)  评论:(0)  加入收藏
C#异步编程:Task.Run vs. async-await,掌握基础与高级用法
概述:C#中的异步编程有两主要方式:Task.Run用于在后台线程执行同步操作,而async-await更适用于清晰表达异步流程。基础用法展示了它们的简单应用,高级用法则演示了它们的结合使...【详细内容】
2024-03-09  架构师老卢  今日头条  Tags:C#   点击:(22)  评论:(0)  加入收藏
C++多线程编程:解锁性能与并发的奥秘
今天我们将深入探讨C++中的多线程编程,揭示多线程如何解锁性能潜力,提高程序的并发性能。什么是多线程?在计算机科学中,多线程是指一个进程(程序的执行实例)中的多个线程同时执行...【详细内容】
2024-02-03     AI让生活更美好  Tags:C++   点击:(68)  评论:(0)  加入收藏
C++代码优化攻略
今天我们将深入探讨C++性能优化的世界。在当今软件开发的浪潮中,高性能的代码是必不可少的。无论是开发桌面应用、移动应用,还是嵌入式系统,性能都是关键。1. 选择合适的数据结...【详细内容】
2024-01-26  AI让生活更美好  微信公众号  Tags:C++   点击:(113)  评论:(0)  加入收藏
C# 线程本地存储为什么线程间值不一样
为什么用 ThreadStatic 标记的字段,只有第一个线程拿到了初始值,其他线程都是默认值,让我能不能帮他解答一下,尼玛,我也不是神仙什么都懂,既然问了,那我试着帮他解答一下,也给后面类...【详细内容】
2024-01-26  一线码农聊技术  微信公众号  Tags:C#   点击:(66)  评论:(0)  加入收藏
C++质数检测器的设计与实现​
质数,作为数学中的一个基本概念,一直以其独特的性质吸引着众多研究者和爱好者。质数是指大于1的自然数中,除了1和它本身以外不再有其他因数的数。在实际应用中,质数检测也扮演着...【详细内容】
2024-01-15  鲨鱼编程  微信公众号  Tags:C++   点击:(110)  评论:(0)  加入收藏
C# 登顶!超越Java或非空想
整理丨诺亚出品 | 51CTO技术栈(微信号:blog51cto)近日,TIOBE编程社区公布年度编程语言,此次摘得这一桂冠的是C#。这也是C#在TIOBE二十多年评选历史中首次赢得这一年度大奖。C#虽...【详细内容】
2024-01-15    51CTO  Tags:C#   点击:(112)  评论:(0)  加入收藏
站内最新
站内热门
站内头条