代码如下:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct A {
A(int i = -1): i_(i) {
wcout << "Constructor: i = " << i_ << endl;
}
A(A const &a) {
wcout << "Copy constructor: i = " << i_ << " a.i = " << a.i_ << endl;
*this = a;
}
~A() { wcout << "Destructor: i = " << i_ << endl; }
A &operator=(A const& a) {
wcout << "Copy assignment operator: i = " << i_ << " a.i = " << a.i_ << endl;
i_ = a.i_;
return *this;
}
bool operator==(A const& rhs) { return i_ == rhs.i_; }
int get() { return i_; }
private:
int i_;
};
int wmain() {
A a[] = {1, 2, 3, 2, 4, 5};
vector<A> v(a, a + sizeof a/sizeof a[0]);
wcout << "==== Just before remove ====" << endl;
remove(v.begin(), v.end(), 2);
wcout << "==== Just after remove ====" << endl;
return 0;
}
输出:
==== Just before remove ====
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 2 a.i = 3
Constructor: i = 2
Destructor: i = 2
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 3 a.i = 4
Constructor: i = 2
Destructor: i = 2
Copy assignment operator: i = 2 a.i = 5
==== Just after remove ====
问题是:为什么在remove()运行时调用析构函数6次?我需要弄清楚这个烂摊子。
注意:请在回答之前在您的系统上执行此代码。 注意:MSVCPP 11
答案 0 :(得分:7)
问题是:为什么析构函数被调用6次而remove()是 运行
总之,析构函数调用与2
隐含地转换为A
remove()
有关。每次这种隐式转换的结果超出范围时,都会调用A
的析构函数。
这些隐式转换的原因是remove()
需要将a
的每个元素与2
进行比较。唯一的方法是拨打A::operator==(const A&)
:
bool operator==(A const& rhs) { ... }
由于rhs
的类型为const A&
,因此编译器为:
A(int)
将2
转换为A
; operator==(const A&)
; A::~A()
来销毁临时。后者是您正在看到的析构函数调用。
如果要将以下比较运算符添加到A
,您将看到这些析构函数调用消失:
bool operator==(int rhs) { return i_ == rhs; }
或者,如果您这样调用remove()
,您将看到所有bar一个析构函数调用消失:
remove( v.begin(), v.end(), A(2) );
最后,如果您要A::A(int)
explicit
,编译器将不允许您使用remove()
作为最后一个参数调用2
(您必须用A(2)
)来表示。
答案 1 :(得分:3)
问题是:为什么析构函数被调用6次而remove()是 运行
因为std::remove
只是重新排序元素,但不会从向量中删除任何内容。在重新排序的过程中,一些元素是复制构造的。
下一段代码详细显示了会发生什么:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct A {
A(int i = -1): i_(i) {cout << "I was created" << i_ << endl;}
~A() {
cout << "I was destroyed" << i_ << endl;
}
A(const A& o):i_(o.i_)
{
cout << "I was copied" << i_ << endl;
}
A& operator=(const A& o)
{
i_=o.i_;
cout << "I was assigned" << i_ << endl;
return *this;
}
int get() { return i_; }
bool operator==(A const& rhs) { return i_ == rhs.i_; }
private:
int i_;
};
int main() {
std::cout<<"start"<<std::endl;
A a[] = {1, 2, 3, 2, 4, 5};
std::cout<<"creating"<<std::endl;
vector<A> v(a, a + sizeof a/sizeof a[0]);
std::cout<<"removing"<<std::endl;
remove( v.begin(), v.end(), 2 );
std::cout<<"end"<<std::endl;
}
remove
将“已移除”元素放在向量的末尾。
答案 2 :(得分:2)
std::remove
被声明为
template<typename ForwardIterator, typename Tp>
ForwardIterator remove(ForwardIterator first, ForwardIterator last,
const Tp& value);
在您的使用中,第三个参数(2)推导为int
。因为int
变量不能与A
对象直接比较,所以首先必须为每个
if (*it == value) ...
在比较结束时,临时被销毁。
(总而言之,你有一个隐藏的性能问题,因为有非显式的单参数,a.k.a。转换构造函数。)
答案 3 :(得分:1)
真的很简单;你只需要了解std::remove
的作用以及它是如何做到的。提示:它不从向量中删除元素。它然后移动到你的收藏的后面。在向量中移动元素涉及破坏原始元素。所以这就是你的析构函数调用的来源(部分)。
另一部分来自临时对象 - 因为您将int
(而不是struct A
的实例)作为最后一个参数传递给std::remove
,所以A的实例必须是为了比较而建造的。如果你想对你的代码强加一些纪律,试着养成为一个参数构造函数添加关键字explicit
的习惯。它在消除这种临时物体方面非常有效。然后,您必须明确创建比较对象:
remove(v.begin(), v.end(), A(2));