在下面的代码中,A类有一个整数向量。 B类继承自A并且具有向量的迭代器。 B的构造函数初始化B的迭代器。但是当我们打印时,我们得到了垃圾。为什么主打印垃圾?我错过了什么吗?
#include <iostream>
#include <vector>
class A {
public:
A() {}
A(const A& copyFrom) : intList(copyFrom.intList) {} //copy constructor
virtual ~A() {}
void populateList()
{
for(int i=10; i<100; i++)
{
intList.push_back(i);
}
}
protected:
std::vector<int> intList;
};
class B : public A {
public:
B() : A() {}
B(const A& a) : A(a), intIt(intList.begin())
{
std::cout << "constructor with base class param called" << std::endl;
}
B(const B& b) : A(b), intIt(intList.begin()) //copy constructor
{
std::cout << "copy constructor called" << std::endl;
}
std::vector<int>::iterator intIt;
};
int main() {
A a;
a.populateList();
B b ;
b = B(a);
std::cout << *b.intIt << std::endl; //prints garbage. why?
}
谢谢!
答案 0 :(得分:4)
B b ;
b = B(a); //the copy constructor of B is not called here. Why?
该行中的操作是赋值,而不是复制构造函数。复制构造函数将是:
B b = someotherB;
请注意,从语义上讲,这也是复制构造:
B b = B(a);
但编译器通常会通过创建B(a)
代替b
来避免复制构造。
答案 1 :(得分:3)
与声明分离的表达式b = B(a)
是赋值,而不是复制构造。这是关于C ++的一个令人困惑的事情:
Foo x(y); // copy-construction
Foo x = y; // also copy-construction, equivalent to the above
Foo x; x = y; // default-construction followed by assignment, different
// (and possibly less efficient) than the above
未能定义赋值运算符将创建执行成员分配的默认运算符。对于intList
成员,这是可以的,但是分配的intIt
将继续指向旧列表,在您的情况下,旧列表属于B
创建的B(a)
的临时引用表达。只要该引用被销毁(位于最外层表达式的末尾),它的intList
就会被销毁,迭代器也会变得无效。
换句话说,B b; b = B(a)
相当于:
B b; // constructor
{
B tmp(a); // copy constructor
b = tmp; // member-wise assignment
// at this point, b.intList is a copy of tmp.intList, but b.intIt
// points to the beginning of tmp.intList, not to the beginning of
// b.intList
}
// tmp is destroyed and b.intIt now points to the beginning of a deleted list
您有责任定义一个赋值运算符,该运算符维护intIt
的不变量,引用intList
中的元素;例如:
B& operator=(const B& rhs)
{
intList = rhs.intList;
intIt = intList.begin() + (rhs.intIt - rhs.intList.begin());
std::cout << "assignment called" << std::endl;
return *this;
}
另请注意,当您更改向量的大小时,向量中的所有迭代器都会失效,即使您只附加push_back()
。因此populateList()
和其他可以更改向量大小的方法应该在B
中重写,以便重新计算intIt
。由于这些原因,最好避免使用迭代器并将位置简单地存储到向量中,并且有一个返回intList.begin() + pos
的函数,从而按需创建一个保证有效的迭代器。然后你不需要既不定义复制构造函数也不定义赋值运算符,因为编译器提供的默认值可以正常工作。
答案 2 :(得分:0)
不会调用复制构造,因为您没有分配相同类的对象
b = B(a); //the copy constructor of B is not called here. Why?
在上面的代码中,将调用构造函数,因为您传递了类型为A的对象
B(const A& a) : A(a), intIt(intList.begin())
{
std::cout << "constructor with base class param called" << std::endl;
}
迭代器值将无法使用,因为您没有初始化它
std::cout << *(b.intIt) << std::endl; //prints garbage. why?
你需要将其初始化
std::vector<X>::iterator iter; //no particular value
iter = some_vector.begin(); //iter is now usable