3

Similar questions have been asked here many times, following the answers didn't solve my problem though.

Let's say I have:

1)Two classes (ACLass and BClass)

2)AClass has a contructor and a destructor

3)BClass has a std::vector member that stores objects of AClass

4)Number of elements in that vector are UNKNOWN beforehand

5)Whenever a BClass method generateObject() is called, the vector expands as a new AClass object is created. This is done by calling std::vector.push_back().

class AClass {
    AClass()  { //Constructor }
    ~AClass() { //Destructor }
};

Class BClass {
    std::vector<AClass> object;

    void generateObject() {
        object.push_back(AClass());
    }
};

Now, in the example above, generateObject() will work only a couple of times. As soon as the vector becomes too small to hold all the objects, it performs various internal operations in order to reserve more memory to be able to expand. The problem is that some of (if not all) the AClass destructors are called during the process.

Since the number of elements is unknown, reserving more space for the vector by using reserve() is not an option. Using emplace_back() did not solve my problem as well.

So how do I go about this? Is there a way to prevent the destructors to be called? Is using std::vector in this case a good idea at all?

Chriss555888
  • 863
  • 1
  • 6
  • 10
  • Would `std::list` suit, instead? – thb Feb 10 '19 at 15:38
  • 2
    *"The problem is that some of (if not all) the `AClass` destructors are called during the process."* Well, yes. Why is this a problem? How else do you expect the vector to relocate its elements? – Igor Tandetnik Feb 10 '19 at 15:38
  • @IgorTandetnik Yes, it might be the way vectors work, but it is still causing problems in my implementation. The topic is how to change the implementation, or what else to implement to get rid of that problem. – Chriss555888 Feb 10 '19 at 15:47
  • @thb Isn't `std::list` quite slow? Is it possible to access `std::list` elements using operator`[]` the same way you can with arrays and vectors? – Chriss555888 Feb 10 '19 at 15:49
  • 3
    Well, what problems, specifically, is it causing in your implementation? Show an example that actually demonstrates said problems. Running destructors on vector reallocation is normal and expected, and isn't generally considered harmful. If it is for you, there must be something unusual about your situation - explain what it is. – Igor Tandetnik Feb 10 '19 at 15:51
  • No, if you need `[]`, you are right. See the solution of @PeteBecker, rather. I do believe that there has been an overreaction since about the year 2010 in computer science against `std::list`; it isn't *that* slow, and the faithful way in which it models certain kinds of problems has inherent value, and if you really need speed you can provide the `list` a local allocator; but the list is in any case probably not the solution you need. – thb Feb 10 '19 at 15:56
  • If it causes problem in your implementation in form of bugs, then those bugs most certainly still exists even if you use `std::list` or `std::deque`, even if they don't lead to a problem in that context. So you still should try to figure out why the code fails with `std::vector`. – t.niese Feb 10 '19 at 16:22
  • If one of your objects being moved or copied causes problems, then your class type is buggy, and needs to fixed, or made non-moveable/non-copyable (at which point it is no longer suitable for use in a vector). It's as simple as that! – Lightness Races in Orbit Feb 10 '19 at 16:48

2 Answers2

6

When std::vector expands its storage it copies or moves the current objects into the new storage space. The old objects are thrown away, and that inherently involves calling destructors on the old objects.

If destroying old objects isn't desirable, std::deque has more or less the same operations as std::vector, with somewhat more overhead, but without having to reallocate storage.

If memory usage is not an issue, std::list never moves its stored objects, but each element in the list also has a pair of pointers, one pointing to the previous element in the list, and one pointing to the next element.

Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • I am definitely going to try that. Thanks for the tip. – Chriss555888 Feb 10 '19 at 15:51
  • A `std::deque` does not reallocate storage? Actually, I had not known that, but had been operating under a false conception of the `deque` all these years. Now that I look in the C++17 standard, though, sect. 26.3.8.4.1 explains, "An insertion at either end of the deque invalidates all the iterators to the deque, but has no effect on the validity of references to elements of the deque." Also, Cppreference.com outlines the data structure by which this effect is achieved. Fascinating. Thanks. – thb Feb 10 '19 at 15:52
  • @Chriss555888 If you find this answer by Pete Becker useful, you might *upvote* and probably *accept* it. That's how to thank Pete. See the orange-arrow and green-check controls at left. – thb Feb 10 '19 at 16:03
  • @PeteBecker `std::deque` is working perfectly, thank you. Solved – Chriss555888 Feb 10 '19 at 16:07
  • But your class type is still buggy, @Chris, and you still need to fix this. What you've done is hacked/worked-around/masked the problem, not solved it.I can't emphasise that enough. – Lightness Races in Orbit Feb 10 '19 at 16:48
  • 1
    @LightnessRacesinOrbit, yes, you are correct. The class used in my program is indeed buggy. The constructor does not initialize evertything the destructor destructs. The other part of the code is initialized by calling init() method externally. For that reason, whenever the vector does its resizing thing (calling destructor and constructor on that object), the init() method is not called and therefore the object is not working properly. Using `std::deque` , as you said, only masked the problem. – Chriss555888 Feb 10 '19 at 17:36
  • @LightnessRacesinOrbit Having said that, I don't see any point in making any changes. The code is working fine, and the design is no longer an obstacle. There also seems not to be any disadvantage to using `std::deque` over `std::vector`. – Chriss555888 Feb 10 '19 at 17:41
  • 1
    @Chriss555888 -- as I hinted, `std::deque` introduces time and space overhead that is not present with `std::vector`. More important, having a class that does not work correctly will, sooner or later, bite you. "It's okay that the brakes on my car aren't very good, because I never drive fast enough to get into trouble." Until you do. Fix it. – Pete Becker Feb 10 '19 at 17:43
  • @Chriss555888 Well, it's your decision. I believe very strongly that it's the wrong one, but it's your call. Good luck. – Lightness Races in Orbit Feb 10 '19 at 20:21
0

Your description of your problem seems to match my problem, which goes somewhat like this:

#include <vector>
#include <iostream>

class A{
public:
    int* array;
    A(){
        array = new int[10];
    }
    ~A(){
        delete[] array;
    }
}

int main(){
    std::vector<A> vector;
    vector.push_back(A());
    vector.push_back(A());

    // Array has been deleted by the destructor, so throws an error
    std::cout << vector[0].array[0];
}

When I try to access the elements of the array, the destructor has deleted it.

The solution to this, whilst still using vector is to define a copy constructor and/or move constructor for the class. (vector can use either depending on the implementation)

For this example, the solution would look like this

#include <vector>
#include <iostream>

class A{
public:
    int* array;
    A(){
        array = new int[10]();
    }
    // Copy constructor
    A(const A& a){
        std::cout << "Copied" << std::endl;
        array = new int[10];
        for(int i = 0; i < 10; i++){
            array[i] = a.array[i];
        }
    }
    // Move constructor
    A(A&& a) noexcept {
        std::cout << "Moved" << std::endl;
        array = a.array;
        a.array = nullptr;
    }
    ~A(){
        delete[] array;
    }
}

int main(){
    std::vector<A> vector;
    vector.push_back(A());
    vector.push_back(A());
    
    // Doesn't throw an error
    std::cout << vector[0].array[0] << std::endl;
    return 0;
}
Ben
  • 13
  • 5