为什么需要这个复制构造函数?

3
#include <iostream>
#include <vector>

using namespace std;

class A {
  private:
    int number;
  public:
    A() {number = 0;}
    A(int nr) {number = nr;}
    //A(const A& rhand) {this->number = rhand.number;}
    int get_number() const {return this->number;}
    A& operator=(const A& rhand) {
      this->number = rhand.number;
      return (*this);
    }
};

class B {
  private:
    vector<A*>* content;
    vector<A*>::iterator currA;
    bool currA_valid;
  public:
    B() {
      content = new vector<A*>;
      currA = content->begin();
      currA_valid = false;
    }
    void push_A(A* nA) {content->push_back(nA); currA_valid = false;}
    void push_A(A nA) {content->push_back(&nA); currA_valid = false;}
    A get_A() {
      if(!currA_valid) {
        currA = content->begin();
        if(!content->empty()) {
          currA_valid = true;
        }
      }
      if(currA == content->end() || this->content->empty()) {
        currA = content->begin();
        return A();
      }
      else {
        A result(**currA);
        ++currA;
        return result;
      }
    }
};

int main()
{
  B container;

  A* a1 = new A(1);
  cout << a1->get_number() << endl;
  A a2(2);
  cout << a2.get_number() << endl;

  container.push_A(a1);
  container.push_A(a2);


  A tmp;
  while((tmp = container.get_A()).get_number() != 0)
    cout << "Inhalt tmp: " << tmp.get_number() << endl;

  return 0;
}

最近我遇到了一个问题,然后写了这段代码。

基本上类B是类A对象的容器。

在实际代码中,A比较大,并且相同类型的A对象可能会出现在容器中多次,因此为了节省空间,B只存储指向A的指针。

B::push函数将A对象插入容器中。

这些函数为指向A的指针和A的值进行了重载。

主函数结尾的while循环是我想要的(有点像使用iostream对象的流操作符)。

B中的迭代器"currA"跟踪上一次由函数调用B::get_A()返回的元素,因此连续调用该函数将返回B::content中的所有A对象,直到达到末尾。在这种情况下,内部迭代器将被重置,并返回具有内部无效标志的A对象(在本例中为简单起见,A::number为0)。

该程序的输出可能如下所示:

1
2
Inhalt tmp: 1 //content of a1
Inhalt tmp: 4620996 //content of a2

主函数实例化了两个对象A a1(1)和A* a2(2)。为了测试A::get_number(),显示它们内部的值。两个都按预期工作。但是在我们将它们都输入容器中并再次从容器中检索它们后,只有a1被正确显示。a2的内容显示了一些随机数。起初我以为是指针的问题,但证明如果像这样声明和定义类A的复制构造函数,问题就可以解决:
A(const A& rhand) {this->number = rhand.number;}

据我所知,如果未提供复制构造函数且类具有指针成员,则C++编译器将隐式定义该函数,并建议实现它,以避免浅拷贝。但在这种情况下,A仅具有一个int类型成员。
我还试图通过其他方式获取容器内容来简化代码,摆脱了B :: get_A()。即使没有实现默认构造函数,问题也消失了。因此,我的问题如下:
1.) 编译器定义的复制构造函数是否与我提供的相似? 并且 2.) 复制构造函数与实际问题有什么关系?实现复制构造函数如何解决问题?

使用初始化列表来初始化类属性,而不是在函数体内进行赋值。 - Manu343726
2
push_back 的第二个重载中,您正在存储指向参数的本地副本的指针,因此在 push_back 结束后,该指针无效。 - Manu343726
2个回答

2
void push_A(A nA) {content->push_back(&nA); currA_valid = false;}

在这里,您通过值获取一个 A 对象。该对象是本地函数。当函数返回时,该对象不再存在,因此您的向量留下了一个无效指针。
您的复制构造函数的实现与解决问题无关,这只是巧合。

我已经注意到了,正如您在评论中所看到的那样,但这不是对OP问题的回答。这只是一条评论。 - Manu343726
它可能不是对楼主问题的回答,但却是解决他问题的方案。 - Benjamin Lindley
不,这不是解决方案。这是与问题相关的错误。问题不是存储指向副本的指针(是的,那是一个错误,但不是所质疑的错误),而是在没有复制构造函数的情况下进行复制。解决方案是建议OP学习三法则。 - Manu343726
@Manu:这是关于问题原因的解释。OP关于复制构造函数的问题与此问题完全无关,所以我忽略了它。 - Benjamin Lindley

0

是的,如果没有提供复制构造函数,编译器会自动写入。但是,如果您定义了自己的赋值运算符或析构函数,则应该定义自己的复制构造函数实现。

通常情况下,如果您编写了这些构造之一的实现(复制构造函数、赋值运算符和/或析构函数),则必须编写其他构造的实现。

这被称为“三大法则”。


这是错误的。三法则存在于我们程序员需要遵循。编译器不遵守任何规则。它可以并且将在所有情况下实现复制构造函数(无论是否定义了赋值运算符或析构函数),除非默认实现会导致定义不明确的情况。 - Benjamin Lindley
@BenjaminLindley 是的,三法则是经验准则,而不是编译器的规则。但这不是重点。重点是学习代码为什么会生成错误,以及解决它的规则。 - Manu343726
"但是如果您定义了自己的赋值运算符或析构函数,编译器将无法提供复制构造函数的实现。" -- 这就是问题所在,而且是错误的。 - Benjamin Lindley

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