运算符重载的基本规则和习惯用法是什么?

2024-02-19

注:答案已在特定订单,但由于许多用户根据投票而不是给出的时间对答案进行排序,因此这里有一个答案索引按照最有意义的顺序:

  • C++ 中运算符重载的一般语法 https://stackoverflow.com/questions/4421706/operator-overloading-in-c/4421715#4421715
  • C++ 中运算符重载的三个基本规则 https://stackoverflow.com/questions/4421706/operator-overloading-in-c/4421708#4421708
  • 会员与非会员之间的决定 https://stackoverflow.com/questions/4421706/operator-overloading-in-c/4421729#4421729
  • Common Operators to Overload https://stackoverflow.com/questions/4421706/operator-overloading-in-c/4421719#4421719
    • 赋值运算符
    • 流插入和提取
    • 函数调用运算符
    • 逻辑运算符
    • 算术运算符
    • 下标运算符
    • 类似指针类型的运算符
  • 比较运算符,包括 C++20 三向比较 https://stackoverflow.com/questions/4421706/operator-overloading-in-c/77055231#77055231
  • 转换运算符 https://stackoverflow.com/questions/4421706/operator-overloading/16615725#16615725
  • 重载 new 和删除 https://stackoverflow.com/questions/4421706/operator-overloading-in-c/4421791#4421791
  • 规范函数签名总结 https://stackoverflow.com/a/77112654

(Note: This is meant to be an entry to Stack Overflow's C++ FAQ https://stackoverflow.com/questions/tagged/c++-faq. If you want to critique the idea of providing an FAQ in this form, then the posting on meta that started all this https://meta.stackexchange.com/questions/68647/setting-up-a-faq-for-the-c-tag would be the place to do that. Answers to that question are monitored in the C++ chatroom https://chat.stackoverflow.com/rooms/10/c-lounge, where the FAQ idea started in the first place, so your answer is very likely to get read by those who came up with the idea.)


要重载的常用运算符

重载运算符的大部分工作都是样板代码。这不足为奇,因为运算符只是语法糖。他们的实际工作可以通过(并且通常被转发到)普通函数来完成。但重要的是您要正确获取此样板代码。如果失败,要么你的操作员的代码将无法编译,你的用户的代码将无法编译,或者你的用户的代码将表现得令人惊讶。

赋值运算符

关于任务,有很多话要说。不过大部分内容已经在GMan 著名的复制和交换常见问题解答 https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom,所以我在这里跳过大部分内容,只列出完美的赋值运算符以供参考:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

流插入和提取

Disclaimer
For overloading << and >> as bitwise shift operators, skip to the section Binary Arithmetic Operators.

按位移位运算符<< and >>尽管仍然在硬件接口中使用它们从 C 继承的位操作函数,但在大多数应用程序中作为重载流输入和输出运算符已变得更加普遍。

The stream operators, among the most commonly overloaded operators, are binary infix operators for which the syntax does not specify any restriction on whether they should be members or non-members. However, their left operands are streams from the standard library, and you cannot add member functions to those1, so you need to implement these operators for your own types as non-member functions2. The canonical forms of the two are these:

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // Write obj to stream
  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // Read obj from stream
  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);
  return is;
}

实施时operator>>,只有当读取本身成功,但结果不是预期的时候才需要手动设置流的状态。

1 Note that some of the << overloads of the standard library are implemented as member functions, and some as free functions. Only the locale-dependent functions are member functions, such as operator<<(long).

2 According to the rules of thumb, the insertion/extraction operators should be member functions because they modify the left operand. However, we cannot follow the rules of thumb here.

函数调用运算符

函数调用运算符,用于创建函数对象,也称为functors https://en.wikipedia.org/wiki/Functor_(functional_programming),必须定义为member函数,因此它总是具有隐含的this成员函数的参数。除此之外,它可以被重载以接受任意数量的附加参数,包括零。

下面是语法示例:

struct X {
    // Overloaded call operator
    int operator()(const std::string& y) {
        return /* ... */;
    }
};

Usage:

X f;
int a = f("hello");

在整个 C++ 标准库中,函数对象总是被复制。因此,您自己的函数对象的复制成本应该很低。如果函数对象绝对需要使用复制成本高昂的数据,那么最好将该数据存储在其他地方并让函数对象引用它。

比较运算符

This section has been moved elsewhere
See this FAQ answer https://stackoverflow.com/questions/4421706/operator-overloading-in-c/77055231#77055231 for overloading the binary infix ==, !=, <, >, <=, and >= operators, as well as the <=> three-way comparison, aka. "spaceship operator" in C++20. There is so much to say about comparison operators that it would exceed the scope of this answer.

在最简单的情况下,您可以默认重载所有比较运算符<=> in C++20 https://en.wikipedia.org/wiki/C%2B%2B20:

#include <compare>

struct X {
  // defines ==, !=, <, >, <=, >=, <=>
  friend auto operator<=>(const X&, const X&) = default;
};

如果您无法执行此操作,请继续查看链接的答案。

逻辑运算符

一元前缀否定!应该作为成员函数来实现。超载通常不是一个好主意,因为它非常罕见且令人惊讶。

struct X {
  X operator!() const { return /* ... */; }
};

The remaining binary logical operators (||, &&) should be implemented as free functions. However, it is very unlikely that you would find a reasonable use case for these1.

X operator&&(const X& lhs, const X& rhs) { return /* ... */; }
X operator||(const X& lhs, const X& rhs) { return /* ... */; }

1 It should be noted that the built-in version of || and && use shortcut semantics. While the user defined ones (because they are syntactic sugar for method calls) do not use shortcut semantics. User will expect these operators to have shortcut semantics, and their code may depend on it, Therefore it is highly advised NEVER to define them.

算术运算符

一元算术运算符

一元递增和递减运算符有前缀和后缀两种形式。为了区分其中一个,后缀变体需要一个额外的虚拟 int 参数。如果重载增量或减量,请确保始终实现前缀和后缀版本。

这是增量的规范实现,减量遵循相同的规则:

struct X {
  X& operator++()
  {
    // Do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

Note that the postfix variant is implemented in terms of prefix. Also note that postfix does an extra copy.1

重载一元减号和加号并不常见,最好避免。如果需要,它们可能应该作为成员函数重载。

1 Also note that the postfix variant does more work and is therefore less efficient to use than the prefix variant. This is a good reason to generally prefer prefix increment over postfix increment. While compilers can usually optimize away the additional work of postfix increment for built-in types, they might not be able to do the same for user-defined types (which could be something as innocently looking as a list iterator). Once you got used to do i++, it becomes very hard to remember to do ++i instead when i is not of a built-in type (plus you'd have to change code when changing a type), so it is better to make a habit of always using prefix increment, unless postfix is explicitly needed.

二元算术运算符

对于二元算术运算符,不要忘记遵守运算符重载的第三个基本规则:如果您提供+,还提供+=,如果您提供-, 不要省略-=, etc. 安德鲁·科尼格 https://en.wikipedia.org/wiki/Andrew_Koenig_(programmer)据说是第一个观察到复合赋值运算符可以用作非复合赋值运算符的基础。也就是说,运营商+是根据以下方面实施的+=, -是根据以下方面实施的-=, etc.

根据我们的经验法则,+及其同伴应该是非成员,而其复合赋值对应者(+=等),改变他们的左参数,应该是一个成员。这是示例代码+= and +;其他二元算术运算符应以相同的方式实现:

struct X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};

inline X operator+(const X& lhs, const X& rhs)
{
  X result = lhs;
  result += rhs;
  return result;
}

operator+= returns its result per reference, while operator+ returns a copy of its result. Of course, returning a reference is usually more efficient than returning a copy, but in the case of operator+, there is no way around the copying. When you write a + b, you expect the result to be a new value, which is why operator+ has to return a new value.1

另请注意operator+可以通过传递稍微缩短lhs按值,而不是按引用。 然而,这会泄漏实现细节,使函数签名不对称,并且会阻止命名返回值优化 where result与返回的对象是同一对象。

有时,实施起来并不切实际@按照@=,例如矩阵乘法。 在这种情况下,您还可以委托@= to @:

struct Matrix {
  // You can also define non-member functions inside the class, i.e. "hidden friends"
  friend Matrix operator*(const Matrix& lhs, const Matrix& rhs) {
    Matrix result;
    // Do matrix multiplication
    return result;
  }
  Matrix& operator*=(const Matrix& rhs)
  {
    return *this = *this * rhs; // Assuming operator= returns a reference
  }
};

位操作运算符~ & | ^ << >>应该以与算术运算符相同的方式实现。但是,(除了超载<< and >>对于输出和输入),很少有合理的用例来重载它们。

1 Again, the lesson to be taken from this is that a += b is, in general, more efficient than a + b and should be preferred if possible.

下标运算符

下标运算符是二元运算符,必​​须作为类成员实现。它用于类似容器的类型,允许通过键访问其数据元素。 提供这些的规范形式是这样的:

struct X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

除非您不希望您的类的用户能够更改由operator[](在这种情况下,您可以省略非常量变体),您应该始终提供运算符的两个变体。

类似指针类型的运算符

要定义您自己的迭代器或智能指针,您必须重载一元前缀解引用运算符*和二进制中缀指针成员访问运算符->:

struct my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

请注意,这些也几乎总是需要 const 和非常量版本。 为了->运算符,如果value_type is of class (or struct or union)类型,另一个operator->()被递归调用,直到operator->()返回非类类型的值。

一元取址运算符永远不应该被重载。

For operator->*() see 这个问题 https://stackoverflow.com/q/8777845/140719。它很少被使用,因此很少过载。事实上,即使迭代器也不会重载它。


继续转换运算符 https://stackoverflow.com/questions/4421706/operator-overloading/16615725#16615725.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

运算符重载的基本规则和习惯用法是什么? 的相关文章

  • WPF 中的屏幕分辨率问题?

    我将在 WPF 中使用以下代码检测分辨率 double height System Windows SystemParameters PrimaryScreenHeight double width System Windows Syste
  • 通过增加索引之和来生成排序组合的有效方法

    对于启发式算法 我需要一个接一个地评估特定集合的组合 直到达到停止标准 由于它们很多 目前我正在使用以下内存高效迭代器块生成它们 受到 python 的启发 itertools combinations http docs python o
  • C 程序从连接到系统的 USB 设备读取数据

    我正在尝试从连接到系统 USB 端口的 USB 设备 例如随身碟 获取数据 在这里 我可以打开设备文件并读取一些随机原始数据 但我想获取像 minicom teraterm 这样的数据 请让我知道我可以使用哪些方法和库来成功完成此操作以及如
  • 为什么Apache MPM prefork.c 使用互斥体来保护accept()?

    我坐下来读书Apache 的 MPM prefork c http code metager de source xref apache httpd server mpm prefork prefork c这段代码使用了一个名为accept
  • 如何尝试/捕获所有异常

    我正在完成由其他人启动的 UWP 应用程序 该应用程序经常崩溃 我总是陷入困境应用程序 at if global System Diagnostics Debugger IsAttached global System Diagnostic
  • 将字符串中的“奇怪”字符转换为罗马字符

    我需要能够将用户输入仅转换为 a z 罗马字符 不区分大小写 所以 我感兴趣的角色只有26个 然而 用户可以输入他们想要的任何 形式 的字符 西班牙语 n 法语 e 和德语 u 都可以包含用户输入中的重音符号 这些重音符号会被程序删除 我已
  • 如何创建用于 QML 的通用对象模型?

    我想知道是否有任何宏或方法如何将 Qt 模型注册为 QObject 的属性 例如 我有AnimalModel http doc qt io qt 5 qtquick modelviewsdata cppmodels html qabstra
  • 对 boost 库的依赖项没有完整路径

    我已经成功构建了动态库 依赖于使用自定义前缀构建和安装的 boost 库 b2 install prefix PREFIX 然而 当我跑步时otool L在我的库中 我得到如下输出 libboost regex dylib compatib
  • HttpWebRequest vs Webclient(特殊场景)

    我知道这个问题之前已经回答过thread https stackoverflow com questions 1694388 webclient vs httpwebrequest httpwebresponse 但我似乎找不到详细信息 在
  • 如何在 C++ 中将 CString 转换为 double?

    我如何转换CString to a double在 C 中 Unicode 支持也很好 Thanks A CString可以转换为LPCTSTR 这基本上是一个const char const wchar t 在 Unicode 版本中 知
  • 两种类型的回发事件

    1 我发现了两篇文章 每篇文章对两种类型的回发事件的分类都略有不同 一位资源说两种类型的回发事件是Changed事件 其中控件实现 IPostbackDataHandler 当数据在回发之间更改时触发 然后Raised事件 其中控件实现 I
  • C# 委托责任链

    为了我的理解目的 我实现了责任链模式 Abstract Base Type public abstract class CustomerServiceDesk protected CustomerServiceDesk nextHandle
  • 为什么 clang 使用 -O0 生成低效的 asm(对于这个简单的浮点和)?

    我正在 llvm clang Apple LLVM 版本 8 0 0 clang 800 0 42 1 上反汇编此代码 int main float a 0 151234 float b 0 2 float c a b printf f c
  • C# 中的常量和只读? [复制]

    这个问题在这里已经有答案了 可能的重复 const 和 readonly 之间有什么区别 https stackoverflow com questions 55984 what is the difference between cons
  • 使用 mingw32 在 Windows 上构建 glew 时“DllMainCRTStartup@12”的多个定义

    我关注了这个主题 使用 mingw 使建筑物在 Windows 上闪闪发光 https stackoverflow com questions 6005076 building glew on windows with mingw 6005
  • 0-1背包算法

    以下 0 1 背包问题是否可解 浮动 正值和 浮动 权重 可以是正数或负数 背包的 浮动 容量 gt 0 我平均有 这是一个相对简单的二进制程序 我建议用蛮力进行修剪 如果任何时候你超过了允许的重量 你不需要尝试其他物品的组合 你可以丢弃整
  • 初始化 LPCTSTR /LPCWSTR [重复]

    这个问题在这里已经有答案了 我很难理解并使其正常工作 基本上归结为我无法成功初始化这种类型的变量 它需要有说的内容7 2E25DC9D 0 USB003 有人可以解释 展示这种类型的正确初始化和类似的值吗 我已查看此站点上的所有帮助 将项目
  • 如何引用解决方案之外的项目?

    我有一个 Visual Studio C 解决方案 其中包含一些项目 其中一个项目需要引用另一个不属于解决方案的项目 一开始我引用了dll
  • 在 C++17 中使用 成员的链接错误

    我在 Ubuntu 16 04 上使用 gcc 7 2 并且需要使用 C 17 中的新文件系统库 尽管确实有一个名为experimental filesystem的库 但我无法使用它的任何成员 例如 当我尝试编译此文件时 include
  • 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同

    System Net WebException 服务器响应 PASV 命令返回的地址与建立 FTP 连接的地址不同 在 System Net FtpWebRequest CheckError 在 System Net FtpWebReque

随机推荐

  • 在 Java 中将 XML 作为字符串读取

    有人可以帮我解决这个问题吗 我想知道如何将这个示例读取为字符串 我知道如何阅读第一个 但不知道如何阅读所有内容 tr tr
  • 如何声明具有多个具有非平凡关系的泛型类型的成员?

    这是我想在我的java代码中编写的内容 private a R extends Result gt MyType a
  • TlbImp.exe 无法注册 lib 文件:“.dll 不是有效的类型库”

    该 dll是FKAtend dll 附带生物识别设备的VB6 0 SDK 该 dll在VB6 0环境下工作正常 但是 当尝试在另一台具有 VS NET 环境的 PC 上注册它时 出现上述错误 我们使用 dependency walker 检
  • 通过在 MATLAB 中计算平均值进行下采样

    假设我有一个包含 44100 个样本的文件 采样频率为 fs 44100 Hz 所以我的文件是 1 秒长 我想将其下采样到 8 Hz 的采样频率 但我不想通过获取每个 44100 8 5512 5 样本并将其保存在新数组中来实现此目的 但通
  • 构建无法从 VisualStudio 运行,但可以从 msbuild 运行

    从 Visual Studio 中的全新控制台应用程序模板中 我编辑了 csproj构建另一个这样的项目
  • 单选按钮多项选择

    我有许多具有不同名称属性的单选按钮 我面临一个问题 每次我单击每个单选按钮时 我都可以选择全部 我正在使用单选按钮重定向到其他页面 我不想只选择一项 我正在使用 twitter bootstrap 选项卡在一页上显示多个内容 只需切换选项卡
  • 未捕获对象错误:无法注入 ngAnimate

    当我尝试像这样注入 ngAnimate 时 我无法实例化我的应用程序 var app angular module musicsa ngCookies ngResource ngSanitize ui router firebase ngA
  • 基本日期之间查询 $gte、$lte 等

    我在 mongo 查询的正确时间格式方面遇到问题 我的问题是 Is time Timemongo ISODate 对象的正确 go 类型 为什么我的时间解析似乎解析完全不同的日期 这是我正在做的事情的完整示例 package main im
  • 如何使用 ngrok 托管 HTML 文件?

    是否可以使用 NGROK 托管 HTML 文件 而不是 Web 应用程序 我真的对 NGROK 一无所知 我只是用它来托管 Twilio 应用程序的服务器 并且想用它来托管我的另一个项目的 HTML 文件 另外 有人知道如何在 Mac 上创
  • CSS 转换在 IE 中不起作用

    我无法在 IE 或 Firefox 中实现此转换 但在 Safari 和 Chrome 中看起来不错 不透明度显示但是即时的 对我来说 下面的 CSS 看起来是正确的 但我看不出它有任何理由可以在 IE 或 Firefox 中工作 我已经使
  • 我在编译时遇到找不到符号错误

    import java io File import java io BufferedReader import java io InputStreamReader import java io import java io InputSt
  • 如何通过 URL 访问 Jenkins 在 lastSuccessfulBuild 中创建的工件?

    我正在使用 Jenkins 管道构建 Android 应用程序 构建成功完成后 它会创建一个 apk 文件 我希望 QA 团队的成员能够下载此文件并在将其上传到 Google 商店之前测试该应用程序 因此我希望这些用户 有权访问 Jenki
  • 将项目添加到不可变的 Seq

    假设我有一个字符串序列作为输入 我想获得一个新的不可变的Seq它由输入的元素和一个项目组成 c 以下是我发现有效的两种方法 assert Seq a b c Seq a b Seq c 这个的问题是 似乎实例化了一个临时序列 Seq c 只
  • tkinter:无法进入条目小部件

    我不明白为什么下面的输入框rackGUI py在我的代码中是静态的 不允许输入任何内容 我相信所有的Entry对象被正确实例化 我将文本变量指定为StringVar 我的直觉告诉我问题在于命令参数create button实例化 但我不太确
  • Jsoup 未下载整个页面

    网页是 http www hkex com hk eng market sec tradinfo stockcode eisdeqty pf htm http www hkex com hk eng market sec tradinfo
  • IdentityServer4分别对每个客户端进行身份验证

    我使用两个不同的客户端 IdentityServer4提供API保护和登录表单 我可以配置客户端以避免单点登录吗 我的意思是 即使我登录了第一个客户端 我也需要登录第二个客户端 我的ID4配置 internal static IEnumer
  • Highcharts 进度条形图

    是否可以在 Highcharts 中创建这样的进度图表 https 0 s3 envato com files 84221450 screenshots weblator responsive charts 7 bootstrap jpg
  • Jackson->Jackson + HttpPost =“无效的UTF-8中间字节”,设置Mime和编码

    我在我的客户端中使用 Apache HTTP 客户端库和 Jackson 当我将 JSON 发布到服务器时 出现错误 org codehaus jackson JsonParseException Invalid UTF 8 middle
  • 完全适合初学者使用 Maphilight?

    我正在尝试使用名为 maphilight 的 jquery 插件 我对使用 jquery 插件完全陌生 我对其他网页设计编码有相当多的经验 甚至对 javascript 有一点经验 但实际上尝试像这样实际使用它 这是第一次 我只是无法让这个
  • 运算符重载的基本规则和习惯用法是什么?

    注 答案已在特定订单 但由于许多用户根据投票而不是给出的时间对答案进行排序 因此这里有一个答案索引按照最有意义的顺序 C 中运算符重载的一般语法 https stackoverflow com questions 4421706 opera