为什么在没有赋值运算符的情况下调用转换构造函数?

Why is conversion constructor called in absense of assignment operator?

今天我的问题是为什么在没有用户提供赋值运算符的情况下调用构造函数?我知道与 = 符号相关的构造函数和赋值运算符之间的区别。如果对象在该行之前不存在,则隐式调用构造函数。

std::string name = "Bjarne"; // implicit constructor

如果对象在该行之前已经存在,则调用赋值运算符。

std::string name;
name = "Bjarne";   // assignment operator

现在我这里有一个class Number,它是一个tagged union。它只是一个数字,表示 intfloat。我定义了多个转换构造函数,使用户可以轻松地将值隐式分配给 Number。我希望他们在这种情况下被调用:

Number num1 = 8;

为了确定何时调用了哪个构造函数,我在构造函数的主体中放置了 print 语句。

#include <cstdlib>
#include <iostream>

using std::cin;
using std::cout;
using std::cerr;
using std::endl;
using std::ostream;


/* Unions can be anonymous (unnamed).
 * This form is used when nesting a union inside a struct or class that contains an extra data member
 * to indicate what the union contains.
 * This arrangement is called a tagged union (or managed union).
 * It can be used as a "polymorphic" class which can be several things at once, for example to create an array
 * of different data types.
 */

class Number {
    friend ostream& operator<<(ostream& os, Number& num);
  public:
    Number(int i = 0) : DATA_TYPE(INTEGER), i(i) { cerr << "Number(" << i << ")\n"; }

    Number(unsigned int u) : DATA_TYPE(INTEGER), i(static_cast<int>(u)) { cerr << "Number((signed) " << i << ")\n"; }

    Number(float f) : DATA_TYPE(FLOAT), f(f) { cerr << "Number(" << f << ")\n"; }

    Number(double d) : DATA_TYPE(FLOAT), f(static_cast<float>(d)) { cerr << "Number((float) " << f << ")\n"; }    

    Number(const Number& rhs) : DATA_TYPE(rhs.DATA_TYPE), i(rhs.i) { cerr << "Number((Number) " << *this << ")\n"; }
  private:
        enum data_type { INTEGER, FLOAT } DATA_TYPE;

    union {
        int i;
        float f;
    };
};


ostream& operator<<(ostream& os, Number& num)
{
    switch (num.DATA_TYPE) {
        case Number::INTEGER:
      cout << num.i;
          break;
        case Number::FLOAT:
      cout << num.f;
          break;
    default:
      cout << 0;
      break;
    }

    return os;
}


int main()
{
    Number num1 = 5;  // conversion constructor
    cout << num1 << endl;
    num1 = 12.5;
    cout << num1 << endl;

    return EXIT_SUCCESS;
}

当我 运行 这段代码时,它会为我产生意想不到的输出:

Number(5)
5
Number((float) 12.5)
12.5

我看到初始化Number num1时,调用了以int为参数的构造函数。然后我打印 num1 的值。然后在下一行似乎调用了以 double 作为参数的构造函数。这很奇怪。在该行之前,num1 已经作为对象存在。所以它应该在逻辑上调用赋值运算符。我没有定义赋值运算符,所以我假设将调用编译器生成的赋值运算符。

我想知道,在没有用户提供赋值运算符的情况下,当我这样做时会发生什么? num1 = something; 编译器提供的默认赋值运算符被调用还是构造函数被调用?当在一个已经存在的对象上调用构造函数时会发生什么?该对象是否获得 "re-constructed"?那么在这种情况下实际发生了什么?

I want to know, in the absence of a user provided assignment operator, what happens when I do this num1 = something; ?

num1 = 12.5;

调用隐式 Number::operator =(const Number&).

So 从 double.

构造一个临时数字

Does the default assignment operator provided by the compiler get called or does a constructor get called?

两者都有。为临时对象而不是现有对象调用构造函数。

What happens when the constructor is called on an object that already exists? Does that object get "re-constructed"? So what actually happens in that case?

您无法调用现有对象的构造函数。您可以做的最接近的事情是 placement-new.

多亏了 Jarod42,我才弄明白了。所以正如他所建议的那样,一个临时的 Number 对象被无形地构造在 = 的右侧,然后调用赋值运算符,并在该行的末尾销毁临时对象。

我将以下代码添加到 Number class 以实际查看发生了什么。

Number& operator=(const Number& rhs)
{
    cerr << "operator=(const Number&)" << endl;
return *this;
}

~Number() { cerr << "~Number()" << endl; }

有了这个main()函数

int main()
{
    Number num1 = 5;
    num1 = 12.5;

    cout << endl;

    Number num2 = 8;
    num1 = num2;

    return EXIT_SUCCESS;
}

生成以下输出:

Number(5)
Number((float) 12.5)
operator=(const Number&)
~Number()

Number(8)
operator=(const Number&)
~Number()
~Number()