Question

#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;
}

Recently I ran into a problem that I turned into this code snippet.

Essentially class B is a container for objects of class A.

In the actual code A is much larger and the same object of type A may appear several times in the container, so in order to save space, B only stores pointers to A.

The B::push functions feed objects of type A into the container.

They are overloaded for pointers and values of A.

The while-loop at the end of the main-function is what I wanted (kind of like the streaming-operator is used with iostream objects).

An iterator "currA" in B keeps track of which element was last returned by the function call B::get_A(), so successive calls to that function return all the A-objects in B::content until the end is reached. In this case the internal iterator is reset and an object A with an internal invalid-flag (In this case for simplicity A::number is 0) is returned.

The output of this program might look like this:

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

The main function instantiates two objects A a1(1) and A* a2(2).

To test A::get_number() both of their internal values displayed. Both work as expected. But after we fed them both into the container and retrieved them from it again only a1 is correctly displayed. The content of a2 shows just some random numbers.

At first I thought, I made a mistake with some of the pointers but it proves that the problem is fixed if one declares and defines the copy-constructor for class A like this:

A(const A& rhand) {this->number = rhand.number;}

As far as I understood the copy constructor will be implicitly defined by the c++ compiler if none is provided and it is recommended to implement it, when the class has pointers as members to avoid shallow copies. But in this case A only has an int.

I also tried to simplify the code by getting rid of B::get_A() and getting the container content by other means. The problem disappeared even without implementing the default constructor. So here are my questions:

1.) Isn't the copy constructor the compiler defines similar to the one I provided?
And
2.) What does the copy constructor even have to do with the actual problem? How does implementing the copy constructor solve the problem?

Était-ce utile?

La solution

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

Here, you are taking an A object by value. This object is local to the function. When the function returns, the object no longer exists, so your vector is left with an invalid pointer.

Your implementation of the copy constructor had nothing to do with fixing the problem, it was just a coincidence.

Autres conseils

Yes, If a copy-constructor is not provided, the compiler writes it. But if you define your own assigment operator, or destructor, you should probably define your own implementation of copy-ctor.

In general, if you write a implementation of one of that constructs (Copy-ctor, assigment operator, and/or destructor) you must have to write your own implementation of the others.

This is known as "The Rule Of Three".

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top