push_back 和 emplace_back 的区别

文章目录

  • 1、vector::push_back
    • 1.1 void push_back(T&& x) ; (C++11)
      • 参数
      • 返回值类型
      • 大小 和 容量
      • 移动左值
      • 用户自定义类型使用 push_back
    • 1.2 void push_back(const T &x);
      • 参数
      • 返回值类型
    • 1.3 如果 vector 的 size 超过当前capacity,push_back 会使迭代器、指针和引用失效
  • 2、vector::emplace_back
    • 参数
    • 返回值类型
    • 自定义类型
    • 如果发生重新分配,则迭代器、指针和引用无效
  • 3、push_back VS emplace_back
    • 区别1:如果构造函数接受多个参数,push_back 只接受该类型的唯一对象,而 emplace_back 则接受该类型构造函数的参数。
    • 区别2:效率
      • built-in type
      • User-defined type
  • 4、参考

1、vector::push_back

push_back 函数的作用是在 vector 容器的末尾添加数据,

函数原型:(两个版本)

void push_back(T&& x) ; (C++11)
void push_back(const T& x);

push_back 允许在没有内存预留的情况下,也能轻松地将数据附加进 vector 中。

在数组中,存储的大小是固定的,如果需要扩大数组大小,必须分配另一个更大的数组,并将内容复制到新数组。这无疑需要额外的代码,而且如果这个过程重复发生,将内容复制到新数组将消耗大量时间,从而极大地减慢程序速度。

为避免使用固定大小的数组时可能发生的所有不可避免的问题,可以将其替换为vector,并在想要向 vector 添加额外数据时调用push_back 函数。因为 vector 可以进行内存调整,所以无论它可能有多大,你都可以放心。

下面给出了一个代码示例。

int arr[3] = {2, 34, 900};

vector<int> vec = {23};
vec.push_back( 34 ); //second element added
vec.push_back( 100 ); //third element added
vecc.push_back( 500 ); //fourth element added
/* ..you can keep adding data **/
for(auto elem:vec ) //accessing vector element using range-based for loop
{
	cout << elem << ” ”;
}

输出:23 34 100 500

可见,vector 可以接收尽可能多的数据,而不用担心存储空间不足(假设有足够的内存)。vector 实际上是数组的完美替代品。

1.1 void push_back(T&& x) ; (C++11)

参数

x —— 添加到 vector 末尾的是右值

返回值类型

void

当要添加的数据是 右值 时,该函数会被调用。由于它接受右值,因此它会移动数据而不是复制数据。

vector<int> vec;

vec.push_back(23); //calls this version

cout << vec[0]; //23 

大小 和 容量

如果此前没有预留存储空间,该函数会扩大容量。但是,如果使用 reserve() 函数预留了存储空间,这个函数就只会增加其大小而不是容量。当你添加的数据超过预留的大小时,容量才会增加。

vector<string> vecSt={“Angry”}, vecSt1;
 
cout << “vecSt capacity before calling push_back =<< vecSt.capacity();
 
vecSt.push_back(“Sad”); //calls this version
 
cout << “vecSt capacity after calling push_back =<< vecSt.capacity( ) ;
 
vecSt1.reserve(3); //Size reserve for three elements
 
vecSt1.push_back( “Jealousy” );
vecSt1.push_back( “Hatred” );
vecSt1.push_back( “Crime” );
 
cout << “vecSt1 capacity after calling push_back =<< vecSt1.capacity( ) ;
 
vecSt1.push_back(“Law”) ;
 
cout << “vecSt1 capacity after adding 4th data =<< vecSt1.capacity( ) ;

输出:

vecSt capacity before calling push_back = 1
vecSt capacity after calling push_back = 3
vecSt1 capacity after calling push_back = 3
vecSt1 capacity after adding 4th data = 6

增加多少容量取决于编译器,在上述例子中,容量在第一次 push_back 调用时增加 2,并在随后的每次 push_back 调用中增加 1。

移动左值

如果你传递的参数是个左值,那么第二个版本的 push_back 会被调用。但是如果你想调用这个版本来移动一个左值到 vector 中,请使用函数 std::move()

vector<int> vec;

int val = 90;

vec.push_back(val) ; ///calls the second version

vec.push_back(std::move(val)); ///calls this version

用户自定义类型使用 push_back

当类型是用户自定义类型:类 或者 结构体。

如果自定义类型的构造函数只有一个参数,可以用 push_back 添加一个该类型的对象或者直接传递构造函数接收的参数类型一样的数据。

class Data {
	int i;
 
public:
	Data(int ii):i(ii) {} 
	~Data ( ) { }
};

vector<Data> vec;

vec.push_back(Data(78)) ;//adding an object of Data

vec.push_back(89); //work fine,Data constructor accept integer 

传递原始数据是可以接受的,因为 push_back 函数将创建该类型的临时对象,该临时对象将再次复制到 vector 中。

如果 vector 类型的构造函数接收的参数超过1个,那就只能用 push_back 添加该类型的对象,而不能直接串原始数据。push_back 不能接收两个参数

class Type
{
	int i;
	string s ;
 
public:
	Type(int ii ,string ss): i(ii), s(ss) { }
	 
	~Type( ) { }
};
 
vector<Type> vec;
 
vec.push_back(Type(78, “Hello”)); //adding an object of Type
 
vec.push_back(89,”World”); //Error!,raw data is passed

1.2 void push_back(const T &x);

参数

x —— 添加到vector 末尾的值

返回值类型

void

如果传递的参数是 左值 或者 引用 时,该版本就会被调用。在调用此函数时,如果 vector 预留了一些存储空间,则容量保持不变,但如果没有,则容量将随着其大小而增加。

vector<int> vec={23}, vec1;
int val = 90 , &ref = val;
 
vec1.reserve(2);
 
cout << “vec capacity before push_back call =<< vec.capacity( ) << endl;
 
vec.push_back(ref);
 
cout << “vec capacity after push_back call =<< vec.capacity( ) << endl;
 
cout << “vec capacity before push_back call =<< vec1.capacity( ) << endl;
 
vec1.push_back(ref);
 
cout << “vec capacity after push_back call =<< vec1.capacity( ) << endl; 

输出:

vec capacity before push_back call = 1
vec capacity after push_back call = 3
vec1 capacity before push_back call = 2
vec1 capacity after push_back call = 2

1.3 如果 vector 的 size 超过当前capacity,push_back 会使迭代器、指针和引用失效

假设没有为 vector 预留存储空间,并且假设另一个迭代器指向该向量。在这种情况下,调用 push_back 函数将增加大小及其容量,这也会使迭代器无效。请看下面给出的代码示例。

vector<char> vecC={‘B’,‘M’};
 
vector<char>::iterator vecCIt=vecC.begin( );
 
cout << vecCIt[0] << ” ” << vecCIt[1] << endl;
 
vec.push_back(‘L’);
 
cout << vecCIt[0];//undefined,vecCIt is invalidated

输出:

B M
undefined value

不过,如果你已经预留了一些存储空间,那么只要添加的元素小于或等于预留大小,迭代器就是有效的。一旦添加的数据超过当前预留容量,迭代器就会失效。

vector<int> vec;
 
vec.reserve(2);
 
vector<int>::iterator vecIt=vec.begin( );
 
vec.push_back(90);
 
cout << vecIt[0] << vec[1] << endl; ///work fine
 
ve.push_back(45);
 
cout << vecIt[0] << ” ” vecIt[1] << endl; ///work fine
 
vec.push_back(505); //adds the 3rd data
 
cout << vecIt[0] endl ; //undefined
cout << vecIt[1] ;//undefined

输出:

90
90 45
undefined value
undefined value

当添加的数据超过预留大小时,就会进行新的分配。重新分配后,存储空间将有新的地址,但迭代器仍指向之前的存储空间,而现在存储空间已失效,因此迭代器失效。

2、vector::emplace_back

功能和 push_back 一样,都是往 vector 的末尾添加数据,它是在C++11版本中新增的。该函数的声明如下:

template<typename… Args> void emplace_back(Args&&… args);

参数

args—— 单个参数或参数列表

返回值类型

viod

使用示例:

vector<int> vec ={23, 56, 89};
 
vec.emplace_back(981);
vec.push_back(1425);
 
for( auto elem:vec )
{
	cout << elem << ” ” ;
}

输出:

23 56 89 981 1425

上述代码中,push_backemplace_back 进行了同样的操作:在vector 的末尾追加数据。

自定义类型

如下是一个 vector 的类型是用户自定义类型(类 或 结构体)的例子:

class AA {
	string ss;
 
public:
 
	AA(string st):ss(st) { }
 
	string getString( ) const { return ss; }
 
	~AA( ) { }
};
 
int main() {

	vector<AA> vecA={ AA(“String”) , “Gift” , AA(“New”) }; ///vecA intialization
 
	vecA.push_back( AA(“happy”) ) ; //AA object is appended
 
	vecA.emplace_back( “Crop” ) ; ///work fine
	 
	vecA.push_back( “Dome” ) ; ///work fine
	 
	for( auto elem:vecA )
	{
		cout << elem.getString() << ” ” ;
	}
 
	cin.get( ) ;
	return 0 ;
}

输出:

String Gift New Happy Dome

自定义类型的构造函数只有一个参数时,push_backemplace_back 都可以直接传递原始数据类型,因为它们都会为该参数创建对象。

如果 vector 类型的构造函数接受一个以上的参数,那么传递给 emplace_back 的参数必须与这些参数 typw 匹配,如果参数类型不匹配,就无法调用构造函数,因此也就无法创建该类型的对象。

class BB {
	int i;
	string st;
 
public:
	BB(int ii , string ss ) : i(ii),st(ss) { }
 
	~BB( ) { }
};
 
vector<BB> vec;
 
vec.emplace_back( 89 , “New” ); //work fine
 
vec.emplace_back( 78 ) ; //error! requires second string argument
 
vec.emplace_back( “Monkey”, 890 ); //error! arguments does not match

如果发生重新分配,则迭代器、指针和引用无效

只有在发生重新分配时,该函数才会使指向 vector 的迭代器、指针和引用失效;如果没有发生重新分配,迭代器、指针和引用仍然有效。只有当 size 大于 vector 的 capacity 时,才会进行重新分配。

vector<int> vec;

vec.reserve( 3 ) ;
 
vector<int>::iterator vecIt = vec.begin( ) ;
 
vec.emplace_back(90);
vec.emplace_back(120);
vec.emplace_back(910);
 
cout << *vecIt << endl ;//iterator is still valid
 
vec.emplace_back( 4560 );
 
cout << *vecIt ; //undefined

代码中连续三次调用 emplace_back 不会重新分配存储空间,因为预留了三个对象的存储空间。但是在第四次调用 emplace_back 时,由于 size > capacity,因此发生重新分配。一旦发生重新分配,存储空间就有了新的内存地址,但迭代器 vecIt 仍然指向先前的存储空间,因此它失效。因此,使用 vecIt 访问内存是未定义的。

3、push_back VS emplace_back

尽管 push_backemplace_back 的唯一用途是在 vector 的末尾添加数据,但是它们也存在一些区别。最微小的区别是 push_back 从它创建开始就是标准 C++ 的一部分,但是 emplace_back 是在 C++11 的时候新加入的。

区别1:如果构造函数接受多个参数,push_back 只接受该类型的唯一对象,而 emplace_back 则接受该类型构造函数的参数。

vector 类型是用户自定义的类型:类或结构体,并且该类型的构造函数接受一个以上的参数时,在这种情况下,调用 push_back 函数需要传递一个该类型的对象。但在调用 emplace_back 时,我们只需传递构造函数类型的参数,而无需传递对象本身。

请注意,只有当构造函数接受一个以上参数时,才会出现这种差异。在只接受一个参数的构造函数中,当调用 push_back 时,我们可以传递构造函数类型的参数,而不是传递对象。在只有一个参数的构造函数中,push_back 接受的是原始数据而不是对象,这是由于 C++11 版本的 push_back 函数所致。C++11 版本允许根据传递的参数构建对象,然后将其推入 vector 容器。

如果构造函数接受两个或多个参数,则必须明确传递该类型的对象。

代码示例:构造函数只接受一个参数

class Test {
	int i ;
 
public:
	Test(int ii): i(ii) { } //constructor
 
	int get const() { return i; }
 
	~Test( ) { }
};
 
int main( ) {
	vector<int> vec={ 21 , 45 };
 
	vec.push_back( Test(34) ) ; //Appending Test object by passing Test object
 
	vec.push_back( 901 ) ; //Appending Test object but int data is passed,work fine
	 
	vec.emplace_back( Test(7889) ); //work fine
	 
	vec.emplace_back( 4156 ) ; //work fine
	 
	for( auto elem:vec ) {
		cout << elem.get() << ” ” ;
	}
	 
	cin.get( );
	return 0;
}

输出:

21 45 34 901 7889 4156

代码示例:构造函数接收两个参数

class New {
	int i;
	string st;
public:
	New(int ii, string s): i(ii) , st(s) { }
	 
	int getInt const() { return i; }
	 
	string getString const () { return st; }
	 
	~New( ) { }
};
 
int main( ) {
	vector<int> vec={ {21,”String”} , New{45 , “tinger”} } ;
	 
	vec.push_back( New(34 , “Happy” ) ) ; //Appending Test object
	 
	vec.push_back( 901 , “Doer” ) ; //Error!!
	 
	vec.emplace_back( New(78 , “Gomu gomu” ) ); //work fine
	 
	vec.emplace_back( 41 , “Shanks” ) ; //work fine
	 
	for( auto elem:vec ) {
		cout<< elem.getInt( ) << ” ” << elem.getString( ) << endl ;
	}
	 
	cin.get( ) ;
	return 0 ;
}

输出:

21 String
45 Tinger
34 Happy
78 Gomu gomu
41 Shanks

第一次 push_back 调用成功是因为我们传递了一个 "New "对象,但第二次 push_back 调用失败是因为 push_back 无法接受两个参数。另一方面,emplace_back 可以接受传递给它的两个参数(第 25 行)。

在接收参数时,emplace_back 会调用构造函数创建一个 New 对象,并将该对象追加到 vector 中,因此调用成功。

底线是,如果使用 push_back,而类型是 "类 "或 “结构体”,且构造函数只接受一个参数,那么就可以传递类的数据,而不用明确传递对象。但是,如果类的构造函数接受一个以上的参数,则只允许传递类的对象。

emplace_back 函数中,无论类是否接受一个或多个参数,你都可以传递对象或类的构造函数接受的数据类型,无论哪种方式,都可以正常工作。我的建议是,你应该优先使用 emplace_back,而不是 push_back

区别2:效率

在这里,"效率 "一词可以有不同的含义,我们将把它理解为 更快地运行代码:产生更少的开销。首先,让我们看看当 vector 类型是内置类型时的情况,然后再看看用户定义类型的情况。

built-in type

如果是内置类型,push_backemplace_back 在效率上没有区别。

User-defined type

如果 vector 类型是类或结构,即用户自定义类型,那么在这种情况下,emplace_back 比 push_back 函数更有效,为什么?

当类型是用户自定义类型时调用 push_back

如果我们尝试直接(在对象创建之前)使用 push_back 将对象追加到 vector 中,那么在此过程中会首先创建一个临时对象。在创建临时对象的过程中会发生三个步骤:

  1. 调用构造函数创建临时对象
  2. 该临时对象复制到 vector
  3. 复制对象后,会调用 destructor 来销毁临时对象。

调用 push_back 时发生的三个过程的验证结果在后文的 "旁注 "部分。

当类型是用户自定义类型时调用 emplace_back

如果使用 emplace_back,则不会创建临时对象,而是直接在 vector 中创建对象。使用 emplace_back,可以避免创建无关的临时对象。因此,性能得到了提升。

class Dat {
	int i;
 
public:
	Dat(int ii):i(ii) { }
	 
	~Dat( ) { }
};
 
vector<Dat> vec;
 
vec.push_back( Dat(89) ); //efficiency lesser
 
vec.push_back( 67 ); //Same as above efficiency is lesser
 
vec.emplace_back( 890 ); //efficiency more

在第一次 push_back调用(第 12 行)和第二次 push_back 调用(第 14 行)中,会出现上述三个步骤,因此性能会降低。

emplace_back 调用中,只发生第一步,因此性能得到提高。

class Bat {
	int i ;
	string ss ;
	char c ;
 
public:
	Dat(int ii , string s, char cc):i(ii) , ss(s) , c(cc) { }
	 
	~Dat( ) { }
};
 
int main () {
	Bat b1( 23 , "S" ,  'd');
	
	vector<Bat> v;
	
	v.push_back( b1 ); //Perforamce same as below
	
	v.empalce_back( 34 , "New" , 'D' ); //Perforamce same as above
	 
	v.push_back( Bat(90 , "String" , 'P') ); //Perforamce lesser
	 
	v.emplace_back( 890 , "true" , 'O' ); //No change in performance

}

在第一次 push_back 调用(第 17 行)中,我们只是传递已经创建的对象,因此不需要临时创建对象,因此性能与 emplace_back 调用相同。

在第二个 push_back 调用(第 21 行)中,需要创建临时对象,因此效率较低。

下面的图示可能会帮助你更好地理解。

push_back 和 emplace_back 的区别_第1张图片

emplace_back 函数调用中省略了复制过程。

旁注:验证调用 push_back 时发生的三个步骤

class A{
public:
	 A() { cout<<"A constructor called!!"; }
	 ~A() { cout<<"\nA destructor called!!"; }
};
 
int main()
{
	vec<A>v ; 
	 
	v.push_back( 78 );
	 
	/** or 
	v.psuh_back( A(890) ); //The output will be same 
	***/
	 
	cin.get();
	retrun 0;
}

输出:

A constructor called!!
A destructor called!!

调用构造函数创建临时对象,然后将临时对象复制到 vector 的存储空间中。最后调用析构函数销毁临时对象。程序输出结果证实了这一点。

如果你使用 emplace_back 运行程序:

v.emplace_back(23);

输出:

Constructor called

析构函数并未被调用。

4、参考

  • vector::emplace_back
  • vector::push_back
  • C++ difference between emplace_back and push_back function

你可能感兴趣的:(C/C++,#,C++STL标准库与泛型编程,push_back,emplace_back)