【问题标题】:Why is this copy constructor necessary为什么这个复制构造函数是必要的
【发布时间】:2013-07-28 16:24:03
【问题描述】:
#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;
}

最近我遇到了一个问题,我把这个代码变成了sn-p。

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

在实际代码中A要大很多,同一个A类型的对象可能会在容器中出现多次,所以为了节省空间,B只存储了指向A的指针。

B::push 函数将 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

main 函数实例化了两个对象 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.) 复制构造函数与实际问题有什么关系?实现复制构造函数是如何解决问题的?

【问题讨论】:

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

标签: c++ copy-constructor default-copy-constructor


【解决方案1】:
void push_A(A nA) {content->push_back(&nA); currA_valid = false;}

在这里,您按值获取A 对象。该对象是函数的本地对象。当函数返回时,对象不再存在,所以你的向量留下了一个无效的指针。

您对复制构造函数的实现与解决问题无关,这只是巧合。

【讨论】:

  • 我已经注意到,正如您在 cmets 中看到的那样,但这不是 OP 问题的答案。它是一条评论。
  • @Manu343726:这可能不是OP问题的答案,但它是他问题的解决方案。
  • 不,这不是解决方案。它是与问题有关的错误。问题不在于存储指向副本的指针(是的,这是一个错误,但不是质疑的错误),是在没有复制构造函数的情况下进行复制。解决方法是建议OP研究三的规则。
  • @Manu:这是对导致问题的原因的解释。 OP关于复制构造函数的问题与这个问题完全无关,所以我忽略了它。
【解决方案2】:

是的,如果没有提供复制构造函数,编译器会编写它。但是,如果您定义自己的赋值运算符或析构函数,您可能应该定义自己的 copy-ctor 实现。

一般来说,如果您编写其中一个构造(复制器、赋值运算符和/或析构函数)的实现,您必须编写自己的其他实现。

这被称为“The Rule Of Three”。

【讨论】:

  • 这是错误的。 us 程序员必须遵循三法则。编译器不遵守这样的规则。它可以并且将在所有情况下实现复制构造函数(无论是否定义了赋值运算符或析构函数),除非默认实现定义不明确。
  • @BenjaminLindley 是的,三法则是经验法则,而不是编译器的规则。但这不是重点。关键是要了解代码产生错误的原因,以及解决它的规则。
  • “但是如果你定义了你自己的赋值操作符,或者析构函数,编译器是不允许提供复制构造函数的实现的。” -- 这就是重点,这是错误的。
猜你喜欢
  • 1970-01-01
  • 2017-03-26
  • 2012-04-29
  • 2023-03-13
  • 2013-04-25
  • 2019-11-05
  • 1970-01-01
  • 1970-01-01
  • 2019-10-06
相关资源
最近更新 更多