2

I have troubling addressing elements inside objects of Class3, please look at this simplified classes:

class Class1 {
public:
    std::vector<Class2> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};

And their constructors:

Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( Class3 ( this->instance, k ) );
    }
}

In main() an object of Class1 is instantiated with its default constructor. Therefore it creates a vector c2v and fills it with 'noi' objects of Class2.

At the same time, when the objects of Class2 are being put into c2v vector, they're instantiated and each one creates a vector c3v and fills it with 'non' objects of Class3.

The code compiles fine but at runtime when accessing public attributes of Class2 from Class3 objects (via this->instance->c2v[0].getSomeAttribute()) the program stops with a EXC_BAD_ACCESS.

Inspecting with a debugger shows that the pointer to c2v[0] gets corrupted (its value becomes 0x0).

I'm a newbie of C++ I was wondering what is the error when trying to instantiate vectors this way. Should I only declare the vectors and populate them in a separate function called after the creation of all instances of Class2 and Class3 is finished?

I'm adding some actual code, hope it won't be too long to read (please understand I've omitted some forward declarations and preprocessor directives):

// global variables
extern char *filename; //not used
extern int nodes;
extern int numProdotti;
extern int classe; //not used
extern int maxNumRange; //not used
extern int *numRanges;
extern int *list ;
extern int *typeProdMarket;
extern int totalQtyDemand; //not used
extern int totNumRanges; //not used

extern struct product {
    int     qty;
    int     cost;
} **prodMarket;

extern struct range {
    int     discount;
    int     startOfRange;
} **rangeMarket; //not used

int main ( int argc, char *argv[] )
{
    Ctqd instance;
    instance.runOpt();
}

class Ctqd {
    void greedySimple();
    void greedySimpleReverse();
    void greedyFromY();
    void greedyByNiceness1();
    void greedyByNiceness2();
    void greedyByStepTier1();
    void greedyByStepTier2();
    void randomLocalSearch1();
    void LocalSearch2opt();
public:
    std::vector<Item> items;
    std::vector<Supplier> suppliers;
    Solution *solution;
    Ctqd();
    ~Ctqd();
    void runOpt();
};

class Supplier {
    Ctqd *instance;
    int id;
    int refTotQty;
    int refDiscount;
    int refDiscountTier;
    double refTotPrice;
    double refUnitPrice;
    double niceness;
    int purTotQty;
    int purDiscount;
    int purDiscountTier;
public:
    std::vector<Offer> offers;
    Supplier ( Ctqd *instance, int id );
    int getId();
    int getRefTotQty();
    int getRefDiscount();
    int getRefDiscountTier();
    double getRefTotPrice();
    double getRefUnitPrice();
    double getNiceness();
    int getPurTotQty();
    int getPurDiscount();
    int getPurDiscountTier();
    void updateStats();
};

class Offer {
    Supplier *supplier;
    int id;
    int refQty;
    double refPrice;
    double niceness;
    int purQty;
public:
    Offer ( Supplier *supplier, int id );
    int getId();
    int getRefQty();
    double getRefPrice();
    double getNiceness();
    int getPurQty();
    void setPurQty ( int qty );
    int remainingQty();
};

Ctqd::Ctqd()
{
    // constructing items vector
    for ( int k = 0; k < numProdotti; ++k ) {
        items.push_back ( Item ( this, k ) );
    }

    // constructing suppliers vector
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) );
    }

    // building solution
    solution = new Solution ( this );
}

Supplier::Supplier ( Ctqd *instance, int id )
{
    this->instance = instance;
    this->id = id;
    // computing total reference quantity
    refTotQty = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotQty += std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    // computing reference discount coefficients
    refDiscount = 0;
    refDiscountTier = 0;

    for ( int r = 0; r < numRanges[ this->id ]; ++r ) {
        if ( refTotQty < rangeMarket[ this->id ][ r ].startOfRange ) {
            break;
        }
        else {
            refDiscount = rangeMarket[ this->id ][ r ].discount;
            refDiscountTier = r;
        }
    }

    //computing total reference price
    refTotPrice = 0;

    for ( int k = 0; k < numProdotti ; ++k ) {
        refTotPrice += prodMarket[ this->id ][ k ].cost * std::min ( list[ k ] , prodMarket[ this->id ][ k ].qty );
    }

    refTotPrice = refTotPrice * ( 1.000 - refDiscount / 100.000 );
    //computing reference unit price
    refUnitPrice = refTotPrice / refTotQty;
    //computing supplier niceness
    niceness = refTotQty / refUnitPrice;
    purTotQty = 0;
    purDiscount = 0;
    purDiscountTier = 0;

    // building offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) );
    }
}

Offer::Offer ( Supplier *supplier, int id )
{
    this->supplier = supplier;
    this->id = id;
    // computing reference quantity
    refQty = std::min ( list[ this->id ] , prodMarket[ this->supplier->getId() ][ this->id ].qty );
    // computing reference price
    refPrice = prodMarket[ this->supplier->getId() ][ this->id ].cost * ( 1.000 - this->supplier->getRefDiscount() / 100.000 );
    // computing niceness of the offer
    niceness = refQty / ( ( prodMarket[ this->supplier->getId() ][ this->id ].cost + refPrice ) / 2 );
    // init purQty to 0
    purQty = 0;
}

This is where I get the EXC_BAD_ACCESS:

int Offer::remainingQty()
{
    return prodMarket[ supplier->getId() ][ id ].qty - purQty;
}

I've done some experiments: Changed the vectors in the Ctqd class and in the Supplier class to vectors of pointer to objects. Problem was solved only partially. Still had a EXC_BAD_ACCESS when constructing the offers objects.

The constructor for Offer class needs data from the Supplier object that created it. I thought that accessing that data while the suppliers vector was still being populated could lead to problems, so I created a little initialize() function in the supplier:

void Supplier::initialize()
{
    // constructing offers vector
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( new Offer ( this->instance, id, k ) );
    }
}

And added this at the end of the Ctqd class constructor:

// init newly built objects
for ( int i = 0; i < nodes; ++i ) {
    suppliers[i]->initialize();
}

Now things seem to be working fine. But i still didn't figure out what the problem exactly was.

Marco Romano
  • 1,169
  • 7
  • 24
  • What are you **really** trying to do? what is the policy for pointer ownership? – Karl Knechtel Sep 30 '11 at 09:16
  • Class1 is just a container for the problem instance. Class2 represents suppliers from which I can buy goods. Class3 represent the offers for goods the suppliers make. Therefore I put all the suppliers in array c2v and each supplier creates its own c3v array and fill it with its offers. Pointer ownerships are all public. – Marco Romano Sep 30 '11 at 09:23
  • 3
    This example works for me. You should post a complete compilable example that gives the error. – hansmaad Sep 30 '11 at 09:33
  • 2
    You're probably copying your instance of `Class1` without updating the instance pointers in the contained `Class2` and `Class3` instances. – JoeG Sep 30 '11 at 09:35
  • Show the actual code that causes the error. – Kerrek SB Sep 30 '11 at 09:50
  • Code added into the main post.Thanks – Marco Romano Sep 30 '11 at 10:17
  • where's the definition for Item class? Also, in the future please post minimal code that causes the error. Your original sample was good, could have written a method that replicates your problem for that instead. – Luchian Grigore Sep 30 '11 at 11:50
  • Sorry didn't post Item class since I thought it was not relevant. However I've done some experiments based on other comments from the answers below (see below). – Marco Romano Sep 30 '11 at 12:12
  • Your code doesn't compile. Once again, you're using classes before they are defined, in contexts which require a complete type (`Ctqd` in `main`, for example). This is **not** legal in C++. In most contexts, the compiler is required to issue an error; in a few cases, such as using the type to instantiate a template, the behavior is undefined. But undefined doesn't mean legal. – James Kanze Sep 30 '11 at 14:44

5 Answers5

1

By far the easiest (and best) fix for this is to use std::deque instead of std::vector. You do not need to use pointers; you can stick with your original plan of pushing objects.

With a deque, push_back is guaranteed not to invalidate references to its elements. Same for push_front, actually. And it still supports constant-time random access (foo[n]).

A deque is usually what you want when incrementally building a container full of objects.

As a general rule, std::deque is probably the best data structure you have never used.

Nemo
  • 70,042
  • 10
  • 116
  • 153
  • +1 for the idea. However, you're not explaining why it actually fails. I for one am more interested in WHY it fails as opposed to finding a workaround. – Luchian Grigore Sep 30 '11 at 14:16
  • @LuchianGrigore We don't have enough code to be sure why it fails, but it's a fairly good guess that it fails because the pointers held in some of the objects point to objects which have been "moved" in some way. `std::deque` doesn't move objects if all insertions are at the ends, so it might help, but I rather suspect that if his program is simulating something, he'll be removing objects from the middle of the vector somewhere, so even `std::deque` won't work. – James Kanze Sep 30 '11 at 14:39
  • And a reminder that even with `std::deque`, the code will be undefined, and will fail to compile with some compilers. You must break the cyclic references somewhere, by using pointers rather than objects. – James Kanze Sep 30 '11 at 14:40
  • @James: Can you explain what you mean by "the code will be undefined"? Which aspect? – Nemo Sep 30 '11 at 15:14
  • §17.4.3.6/2: "In particular, the effects are undefined in the following cases: [...]--— if an incomplete type (3.9) is used as a template argument when instantiating a template component." In practice, you'll usually get either a compiler error or the code will work: in this case, you'll get a compiler error from g++, at least with the options I usually use. (I think it's `-D_GLIBCXX_CONCEPT_CHECKS` which triggers the error here.) The committee wanted to make it a diagnosable error, but that required concepts, which didn't make the cut. – James Kanze Sep 30 '11 at 15:30
  • @James: But he can fix that just by re-ordering the declarations, right? (Class3 then Class2 then Class1) – Nemo Sep 30 '11 at 15:50
  • @Nemo Perhaps, but it still doesn't solve the problem that his objects actually need reference semantics: you don't want copies. – James Kanze Sep 30 '11 at 17:03
  • @James: Perhaps, but not necessarily. Just because an object contains a pointer does not mean it cannot be copied... – Nemo Sep 30 '11 at 17:25
  • @Nemo The fact that an object contains a pointer doesn't mean that it cannot be copied. It (usually) does mean that the object it points to shouldn't be copied. – James Kanze Oct 03 '11 at 08:55
0

You're not obeying the rule of 3. You should implement a destructor, copy constructor and assignment operator for all your classes.

Consider the line:

c2v.push_back ( Class2 ( this, i ) );

This will create a temporary object, make a copy of it and put it in the vector. The copy and the temporary object will point via the instance member to the same location. However, when the temporary object is destroyed (right before the next line), that memory is freed and available to use. So now you will have inside your vector an object of type Class2 with an invalid field.

This is just my guess. Please post the code in main if this doesn't help.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
  • Which one field will be invalid? – GreenScape Sep 30 '11 at 09:39
  • 2
    The rule of three does not seem to be an issue here. – sellibitze Sep 30 '11 at 09:44
  • I don't believe this is correct - I think the problem is with the (compiler generated) copy constructor of `Class1` - all child objects will point to the original `Class1` rather than the copy. – JoeG Sep 30 '11 at 09:45
  • Unless he gives us the full, compilable, running code, we can only speculate, right? – Luchian Grigore Sep 30 '11 at 09:52
  • I'd like to post the full code but it's rather large and most parts of it will be just useless to this matter. If you don't mind I'll do it. – Marco Romano Sep 30 '11 at 09:54
  • Code posted at the top of the page. Thanks – Marco Romano Sep 30 '11 at 10:17
  • The rule of three would be relevant if the pointers in the objects pointed to logically owned objects. From what I understand, this isn't the case; the problems aren't due to anything that the rule of 3 might correct, but to copying objects which shouldn't be copied (because other objects hold there addresses, if for no other reason). – James Kanze Sep 30 '11 at 14:34
0

Change

std::vector<Class2> c2v;

to

std::vector<Class2 *> c2v;

and

std::vector<Class3> c3v;

to

std::vector<Class3*> c3v;

The problem is that you are taking adresses of local objects inside vector. When vector need more space it reallocates his memory and therefore Class2 and Class3 objects having their adresse changed.

class Class1 {
public:
    std::vector<Class2 *> c2v;
    Class1();
};

class Class2 {
    Class1 *instance;
    int id;
public:
    std::vector<Class3 *> c3v;
    Class2 ( Class1 *instance, int id );
};

class Class3 {
    Class1 *instance;
    int id;
public:
    Class3 ( Class1 *instance, int id );
};


Class1::Class1()
{
    for ( int i = 0; i < noi; ++i ) {
        c2v.push_back ( new Class2 ( this, i ) );
    }
}

Class2::Class2 ( Class1 *instance, int id )
{
    this->instance = instance;
    this->id = id;

    for ( int k = 0; k < nok; ++k ) {
        c3v.push_back ( new Class3 ( this->instance, k ) );
    }
}

And don't forget to cleanup in Class1' and Class2' destructors.

EDIT: Why such strange behaviour occured:

Step 1

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- Supplier's constructor is invoked here
                                                // to construct automatic object of Supplier class
    }
}

Step 2. We are inside of the automatic object's constructor:

Supplier::Supplier ( Ctqd *instance, int id )
{
    for ( int k = 0; k < numProdotti; ++k ) {
        offers.push_back ( Offer ( this, k ) ); // consider that we are passing this to 
                                               // Offer's constructor. this is a pointer to
                                              // automatic variable
    }
}

Step 3. Back to step 1. But now we have automatic object Supplier created

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- automatic object of class Supplier
                                                     // is created. Now as long as push_back() taking
                                                    // parameter by value yo are passing copy of
                                                   // automatic object to push_back();
                                                  // as long as you didn't define copy constructor
                                                 // for Supplier compiler adds default for you.
    }
}

Step 4. Copy of automatic object is saved to suppliers vector. New object (copy of automatic object) has vector offers with exactly same values as our automatic object had (thx to vector's copy constructor). So each object has member supplier that points to... (geass what :)) correct! they all point to automaic object.

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); // <-- magic here
    }
}

Step 5. Automatic object has been destructed(remember how you passed point to this object to Offer's constructor?). Oops you say.

Ctqd::Ctqd()
{
    for ( int i = 0; i < nodes; ++i ) {
        suppliers.push_back ( Supplier ( this, i ) ); 
                                                      // <-- automatic doesn't exist no more
    }
}

So what will happen if one of the supplier's methods gonna try to access Supplier object via supplier member (that we, smart kids, know points to dead object... so sad... QQ). Guess. I believe it will die by realising he sees dead object first time in his life ;).

GreenScape
  • 7,191
  • 2
  • 34
  • 64
  • 1
    He's not taking addresses but values. After your change he would be taking addresses (as in pointers)... – Luchian Grigore Sep 30 '11 at 09:35
  • @LuchianGrigore He's not taking addresses, so he ends up copying noncopyable objects. He should be taking addresses, and the objects should probably be dynamically allocated. – James Kanze Sep 30 '11 at 09:56
  • You're telling using vectors of pointer to objects instead of vectors of objects, right? – Marco Romano Sep 30 '11 at 11:11
  • @Marco Romano Correct. Because, as was mentioned by someone above, Object may be deleted or copied therefore - having their position changed. So when you refer to the memory address that does not belong to that object any more - you are having undefined behaviour. In worst - cases segmentation fault if that memory segment, you are referring to, was detached from virtual address space. So pointers here - is the only correct way. Just remember to free them! – GreenScape Sep 30 '11 at 11:18
  • Looks like my issue is related with this? http://stackoverflow.com/q/580053/972721 – Marco Romano Sep 30 '11 at 11:34
  • Exactly. This is very similar case. – GreenScape Sep 30 '11 at 11:54
  • I've done some experiments that lead to mixed results, please see the new lines added at the bottom of the original post. Thanks – Marco Romano Sep 30 '11 at 12:21
  • Okay, i suppose problem is solved. I'll edit my post to explain why was that happening. – GreenScape Sep 30 '11 at 12:32
  • Oh gosh, it took me a while to understand but it seems I now get it. This explains why the suppliers could be accessed by going forward from Ctqd but not going backwards (well.. not always.. because some parts of the memory occupied by the dead automatic objects were still correctly readable, some not.. I suppose this is what "undefined behavior" means :) ) Correct me if I'm wrong: this also means that using vectors of pointers to objects wouldn't change this behavior, because the 'supplier' attribute inside Offer objects would still be pointing to a dead automatic object. Thanks – Marco Romano Sep 30 '11 at 17:39
  • You would change behaviour because no automatical object will be created. You will need to replace `suppliers.push_back ( Supplier ( this, i ) );` with `suppliers.push_back ( new Supplier ( this, i ) );`. So object will be dynamically allocated. And from now on you are one who is responsible for it's life-cycle. – GreenScape Sep 30 '11 at 22:57
0

For starters, your code won't compile: when the compiler encounters the declaration of Class1::c2v, Class2 is an unknown symbol. If I add the forward declarations (and a definition of noi), it still contains undefined behavior, and doesn't compile with g++, at least not with the usual options. It's illegal to instantiate a standard template with an incomplete type. Since your types have a cyclic dependency, you're going to have to use pointers somewhere.

Also, what happens to the pointers in Class2 and Class3 when the objects are copied (and std::vector will copy). From the comment you made (Class2 represents suppliers, and Class3 represents offers), these classes should probably be noncopyable. This depends a lot on the design, but in most cases, entity classes, which model the problem domain, should be noncopyable. This too leads to using pointers to them, rather than copies. (Reference semantics, rather than value semantics.)

Of course, using pointers does introduce issues of object lifetime. You'll have to decide when and how suppliers and offers come into existence, and when they cease to exist. This should be part of your design.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • Sorry for not including forward declarations. You're actually right, with these classes I was trying to model my problem from an entity-relation diagram. Are you suggesting that I should turn those vectors of objects into vectors of pointer to objects? I've put some more code in the main post if it might help. – Marco Romano Sep 30 '11 at 11:08
  • @MarcoRomano Yes. First, if the objects in question are entity objects, they should probably be dynamically allocated anyway, since typically, their lifetime doesn't correspond to any logical program scope. And typically, they shouldn't be copyable, although that depends somewhat more on context. And finally, regardless of the semantics, you really don't have a choice; instantiating `std::vector` on an incomplete type is undefined behavior, so a forward declaration doesn't suffice; there is no legal solution other than to use pointers. – James Kanze Sep 30 '11 at 14:29
  • And I'm curious about the downvote, since I'm the only one who pointed out the fact that without pointers, the code has undefined behavior. (I note that the other person who proposed pointers also got a downvote, although there is no other legal solution.) – James Kanze Sep 30 '11 at 14:30
  • I didn't put the down vote (I'm a stackvoerflow noob: is the question author the only one to be able to vote answers?) – Marco Romano Sep 30 '11 at 15:54
  • @MarcoRomano I didn't say you did. I put the comment in a separate comment intentionally, to address it more generally. (I'll admit that I don't understand the voting here. It seems rather arbitrary, with bad answers sometimes getting a lot of votes, and really good answers sometimes getting no votes, or even negative ones.) – James Kanze Sep 30 '11 at 17:06
0

Whereas the relocation of the vector's contents and the copying of Class2 or Class3 objects shouldn't give any problems (in contrast to what other answers may state), the problem arises when you create a local object of Class1 and copy this around. When you now try to access the original Class1 object by one of the instance pointers, this object may already have been destroyed and therefore the access to one of its members causes a segfault.

Christian Rau
  • 45,360
  • 10
  • 108
  • 185
  • Actually class1 is a kind of "mother object". It gets instantiated from main only once and is never destroyed (it ends when the program ends). Maybe I shouldn't' have made Ctqd a class but just a namespace with static functions and attributes inside? (sorry for the maybe incorrect terms, I've only used Java before.) – Marco Romano Sep 30 '11 at 12:30
  • @MarcoRomano It depends, but in general, you don't need a single "Application" object in C++, as you would in Java. (On the other hand, it is sometimes better to use one than to use a lot of global data.) – James Kanze Sep 30 '11 at 14:35