C++矢量推回方法和临时对象创建

C++ vector pushback method and temporary object creation

本文关键字:临时对象 创建 方法 C++      更新时间:2023-10-16

下面是C++中向量推回方法的签名。

 void push_back (const value_type& val);

下面是代码

class MyInt
{
   int *a;
public:
  MyInt(int n):a(new int(n)){cout<<"constructor called"<<endl;}
   ~MyInt(){cout<<"destructor called"<<endl;}
   void show(){
   cout<<*a<<endl;
   }
 };

 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);
    v.push_back(m1);   
    v.push_back(m2);
 }

Output
-------------
constructor called
constructor called
destructor called
destructor called
destructor called
1
2
destructor called
destructor called

这里我们看到,对于vector_test函数中创建的2个对象,导致2次构造函数调用。但对于析构函数,它被调用了5次。

现在我的疑虑和问题是

  1. 为什么构造函数和析构函数调用的数量不匹配
  2. 我知道有一些临时对象正在创建中。但这种机制是如何运作的呢
  3. 只是为了测试,我试图提供一个复制构造函数,但它导致编译失败。原因可能是什么
  4. 是否有一些逻辑将m1和m2的内容复制到新对象中并放入容器中

如果有人能详细解释一下,我将不胜感激。谢谢

您还需要为复制构造函数添加日志记录

MyInt(const MyInt& rhs):a(new int(*rhs.a)){cout<<"copy constructor called"<<endl;}

编译器允许省略一些构造函数调用,例如在以下行中:

MyInt m1 = 1;

您可能希望首先调用复制构造函数来实例化临时MyInt(1),然后使用此临时调用复制构造函数。所以你会看到:

constructor called // for temporary
copy constructor called // for m1
destructor called       // for m1
destructor called  // for temporary

但是由于复制省略,编译器将使用MyInt(int n)构造函数直接实例化您的m1实例,即使您的复制构造函数有副作用(使用std::cout)。因此不会出现上述// for temporary日志。

要在gcc中看到这一点,请使用:-fno-elide-constructors选项,该选项将复制构造函数省略。

同样,让MyInt(int n)这样的构造函数显式也是一种很好的做法,这是为了不允许错误地创建MyInt对象实例——你必须让它显式,即MyInt var; var = static_cast<MyInt>(1);

您必须记住,如果不提供五个方法,编译器可能会添加它们。在这种情况下,您会受到编译器生成的副本构造函数的影响。

#include <iostream>
#include <vector>
using namespace std;
class MyInt
{
   int *a;
public:
  MyInt(int n):a(new int(n)){cout<<"constructor called"<<endl;}
  MyInt(MyInt const& copy): a(new int(*copy.a)) {cout<<"copy constructor called"<<endl;}
  MyInt(MyInt&& move): a(move.a) {move.a = nullptr; cout<<"move constructor called"<<endl;}
   ~MyInt(){cout<<"destructor called"<<endl;}
   void show(){
   cout<<*a<<endl;
   }
 };

 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);
    v.push_back(m1);
    v.push_back(m2);
 }
 int main()
 {
    vector<MyInt> v;
    vector_test(v);
 }

当我运行这个时,我得到

batman> ./a.out
constructor called
constructor called
copy constructor called
copy constructor called
copy constructor called
destructor called
destructor called
destructor called
destructor called
destructor called

注意:您正在从析构函数中泄漏内存。

这也是我们有emplace_back()接口的原因。这将减少所创建对象的副本数量。此外,当启用优化时,您将看到其中一些对象没有被复制,而是在原地创建。

 void vector_test(vector<MyInt> &v)
 {
    MyInt m1(1);
    MyInt m2(2);
    // Can use emplace back
    v.emplace_back(1); // Created the object in-place in the vector.
    // Or you can use a temorary.
    v.push_back(2);  // Compiler sees there is a single argument constructor
                     // and will insert the constructor to convert a 2 into
                     // MyInt object.
    v.push_back(MyInt(3)); // Or you can create a temporary explicitly.
 }