【问题标题】:I don't understand when the constructor is called in this example我不明白这个例子中什么时候调用了构造函数
【发布时间】:2019-11-18 16:51:53
【问题描述】:

您好,我一直在尝试分析以下使用操作重载的代码。

#include <iostream>
using namespace std;

#define DBG(str) cout << str << endl

class Integer {
    int n;
public:
    Integer(int _n) : n(_n) { DBG("A"); };
    Integer(const Integer& i) : n(i.n) { DBG("B"); };

    Integer& operator=(const Integer& i) { DBG("C"); n = i.n; return *this; };
    Integer& operator+=(const Integer& i) { DBG("D"); n += i.n; return *this; };

    friend Integer operator+(const Integer& a, const Integer& b);
    friend Integer operator*(const Integer& a, const Integer& b);
    friend ostream& operator<<(ostream& os, const Integer& i);

};

Integer operator+(const Integer& a, const Integer& b) {
    DBG("E"); return Integer(a.n + b.n);
}
Integer operator*(const Integer& a, const Integer& b) {
    DBG("F"); return Integer(a.n * b.n);
}
ostream& operator<<(ostream& os, const Integer& i) {
    DBG("G"); os << i.n; return os;
}

int main() {
    Integer n1(1), n2(2);
    Integer n = 5 + n1 + 2 * n2;

    cout << n << endl;
}

结果是……

A // constructor called when n1 is created
A // constructor called when n2 is created
A // when is this called?
A // when is this called?
F // called when 2 * n2 is operated
A // called when Integer(a.n * b.n) is created in the multiplication function
E // called when 5 + n1 is operated
A // called when Integer(a.n + b.n) is created in the addition function
E // called when (5 + n1) + (2 * n2) is operated
A // called when Integer is created in the addition function
G // called when n is printed using cout
5 // value of n

现在我最麻烦的是A的第三和第四个打印。在Integer n = 5 + n1 + 2 * n2;这句话中,对象n正在被创建并且正确的值被分配给n,所以应该调用一个复制构造函数?我认为应该发生的是应该调用构造函数来制作(5 + n1 + 2 * n2) 的临时对象,然后通过将其复制到n,应该调用复制构造函数。我理解错了什么?

你能解释一下发生了什么吗?提前谢谢你。

【问题讨论】:

  • 让你的构造函数打印出n的值来帮助追踪发生了什么。
  • 你能在 DBG("A") 上放置一个断点并检查调用堆栈吗?
  • Integer n = 5 + n1 + 2 * n2; 5 被转换为 Integer 对象,该对象打印出 A2 也是如此
  • 您应该在调试器中单步执行代码以查看发生了什么。也就是说,我的猜测是第三个和第四个“A”标记来自将 5 和 2 转换为Integer。您对创建 n 的复制构造函数是正确的。
  • 您可以尝试使您的Integer(int _n) 构造函数explicit 并查看编译器指向错误的位置。

标签: c++


【解决方案1】:

问题出在这一行:

Integer n = 5 + n1 + 2 * n2;

对于intInteger 没有operator+,但编译器默默地从intInteger 做了一个implicit conversion,因为默认情况下所有单参数构造函数都可以用作转换方法。 这里是your live code(稍微改进了一点)。

这是 C++ 的危险特性之一,因此在构造函数之前要使用 explicit 关键字。如果你添加它:

explicit Integer(const Integer& i) : n(i.n) { DBG("B"); };

编译器将报告错误,因为现在无法执行隐式转换并且没有operator+(int, const Integer&amp;)operator*(int, const Integer&amp;)

【讨论】:

  • 感谢您的明确答复。因此,如果 int 被隐式转换为 Integer,那么 i.n 的真正含义是什么? 5可以有成员变量吗?
  • 定义构造函数,默认编译器可以使用单参数调用作为转换方法。在这种情况下,编译器会创建临时的未命名对象,该对象将在执行完一行后自动销毁。
  • 有分解隐式代码的工具:cppinsights.io/s/c11fe56f
【解决方案2】:

您的operator+operator* 函数接收const Integer&amp; 作为参数。

计算此行时 (5 + n1 + 2 * n2) 5 和 2 将自动转换为 Integer

如果您不希望它们被转换,您应该考虑为int 创建运算符作为参数。

编辑:您也可以使用显式构造函数。

例如:explicit Integer(int _n){...}

//感谢@foreknownas_463035818的评论

【讨论】:

  • 最好将构造函数声明为显式以防止隐式转换
【解决方案3】:

Friend 函数仅在运算符函数的第一个参数不是为其定义运算符函数的目标类的对象时才用于运算符重载。 在你的情况下,Integer:

Integer operator+(const Integer& a, const Integer& b) {
    DBG("E"); return Integer(a.n + b.n);
}
Integer operator*(const Integer& a, const Integer& b) {
    DBG("F"); return Integer(a.n * b.n);
}

,第一个参数确实是Integer 类型的对象,它是您的目标类。您可能想要的是能够将您的 Integer 对象与原始整数类型一起用作操作数。 将您的朋友函数签名更改为:

Integer operator+(const int& a, const Integer& b) {
    DBG("E"); return Integer(a + b.n);
}
Integer operator*(const int& a, const Integer& b) {
    DBG("F"); return Integer(a * b.n);
}

请注意,a 用于代替 a.n,因为当运算符函数被定义为友元时,两个操作数都作为参数传递。(与第一个操作数是调用对象的非友元运算符函数不同)。所以a 包含int 类型的值,而不是Integer 类型的值。

对包含DBG("A") 的构造函数的匿名调用是5 和2 从int 转换为Integer 时发生的隐式转换的结果。

尽管您的代码可能有效,但这是不可靠的。 希望这会有所帮助。

【讨论】:

  • 谢谢大家的精彩回答
猜你喜欢
  • 2016-08-15
  • 1970-01-01
  • 1970-01-01
  • 2013-06-21
  • 2011-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-08
相关资源
最近更新 更多