构造函数被多次调用

3
我写了下面的c++代码,试图理解c++中的复制省略。
#include <iostream>
using namespace std;

class B
{
  public:  
  B(int x  ) //default constructor
  {
    cout << "Constructor called" << endl;
  }    

  B(const B &b)  //copy constructor
  {
     cout << "Copy constructor called" << endl;
  } 
};

int main()
{ 

  B ob =5; 
  ob=6;
  ob=7;

  return 0;
}

这将产生以下输出:
Constructor called
Constructor called
Constructor called

我不太理解为什么每次给对象 ob 赋值时都会调用构造函数三次。涉及 IT 技术相关内容。

构造函数省略的问题在于它是语言允许的一种优化,而不是语言要求。这意味着它并不总是被执行(取决于您的优化级别等因素)。特别地,通常情况下,如果构造函数调用具有副作用(像这里的情况),编译器通常不会省略它们。 - Cameron
隐式转换可以使用构造函数。在这里,由于int可以通过构造函数B(int)隐式转换为B,因此该构造函数在所有三行中都被调用。如果没有复制省略,则第一行也可以调用复制构造函数(B(const B&))。 - David G
顺便说一下,你的第一个构造函数在技术上被称为自定义构造函数(默认构造函数可以不带参数调用)。 - Cameron
旁注:B(int x)不是默认构造函数。默认构造函数是B() - user2249683
1
额外注释:您可以尝试使用“explicit B(int x)”进行实验 - user2249683
3个回答

5

B ob =5;

这使用了给定的构造函数。

ob=6;

由于没有 B& operator=(int) 函数,而且必须将 6 转换为 B 类型,因此这使用了给定的构造函数。其中一种方法是暂时构造一个 B 并在赋值中使用它。

ob=7;

与上面的答案相同。

我不明白为什么每次赋值都调用了三次构造函数

如我上面所述,你没有 B& operator=(int) 函数,但编译器可以 自动 提供一个复制赋值运算符(即 B& operator=(const B&);)。编译器生成的赋值运算符被调用,并且它需要一个 B 类型,所有的 int 类型都可以通过你提供的构造函数转换为 B 类型。

注意:您可以通过使用explicit(即,explicit B(int x);)来禁用隐式转换,并且我建议除非需要隐式转换,否则使用explicit

示例

#include <iostream>

class B
{
public:
    B(int x) { std::cout << "B ctor\n"; }

    B(const B& b) { std::cout << B copy ctor\n"; }
};

B createB()
{
    B b = 5;
    return b;
}

int main()
{
    B b = createB();

    return 0;
}

示例输出

注意:使用Visual Studio 2013(发布版)编译

B ctor

这表明复制构造函数被省略了(即,在createB函数中,触发了B实例,但没有其他构造函数)。

在C++中是否有特殊规定,以便在这种情况下创建临时对象并调用一个参数构造函数(如果明确给出),而不是出现错误,指出原始类型int无法分配给类对象? - Abhay Sharma
2
您可以通过使用 explicit(例如,explicit B(int x);)来防止隐式转换,这会导致 B b = 5 产生编译错误。除非您想要隐式转换,否则我建议使用 explicit - James Adkison

4
每次你给类型为B的变量ob赋一个整数值时,你实际上是在构造一个新的B实例,从而调用构造函数。想一想,如果不通过以int为参数的构造函数创建B实例,编译器怎么知道如何创建呢?
如果你为类B重载了以int为参数的赋值运算符,它将被调用:
B& operator=(int rhs)
{
    cout << "Assignment operator" << endl;
}

这将导致第一行代码:B ob = 5; 使用构造函数,而接下来的两行将使用赋值运算符,请看下面的示例:
Constructor called
Assignment operator
Assignment operator

如果你不想在赋值时调用带有 int 的构造函数,你可以这样声明为 explicit

http://ideone.com/fAjoA4

explicit B(int x)
{
    cout << "Constructor called" << endl;
}

这会导致编译器在处理您的代码时出现错误,因为它不再允许从整数隐式构造B的实例,而必须显式地进行操作,就像这样:
B ob(5);

顺便提一下,您的构造函数采用 int 作为参数,不是默认构造函数,默认构造函数是可以不带参数调用的构造函数。


3
您没有考虑到赋值运算符。由于您没有定义自己的operator=()实现,编译器会生成一个默认的operator=(const B&)实现。因此,您的代码实际上执行以下逻辑:
#include <iostream>

using namespace std;

class B
{
  public:  
  B(int x) //custom constructor
  {
    cout << "Constructor called" << endl;
  }    

  B(const B &b)  //copy constructor
  {
     cout << "Copy constructor called" << endl;
  } 

  B& operator=(const B &b)  //default assignment operator
  {
     return *this;
  } 
};

int main()
{ 
  B ob(5); 
  ob.operator=(B(6));
  ob.operator=(B(7));

  return 0;
}

编译器生成的 operator=() 运算符期望一个 B 对象作为输入,但你却传递了一个 int 值。由于 B 有一个非 explicit 构造函数,接受一个 int 作为输入,编译器可以自由地使用一个临时对象,从 int 隐式转换到 B
这就是为什么你看到你的构造函数被调用了三次 - 这两个赋值操作创建了临时的 B 对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接