0

I implement a class that stores a number of Parent objects inside an std::map. Each Parent has a Child. That Child has a pointer to it's Parent, which is set in Parent's constructor.

It goes like this: I call std::map::operator[], it calls Parent's constructor, which sets child.parent to this, and returns me the Parent. Should be okay, but if you'd compare the returned Parent's address with the address that it's child stores, they wouldn't match. Meaning that the Child has an invalid pointer.

So when I initialize a Parent through std::map::operator[], it's Child's parent pointer is invalid.

A little demo:

//
// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <cstdlib>


// Just some forward declaration
struct Parent;


/** 
 * Child dummy (just to model my data structure)
 */
struct Child 
{ 
    /**
     * Pointer to it's parent (that gets initialized in Parent's constructor)
     */
    Parent * parent;

    /**
     * Original parent's uid
     */
    int originalParentUniqueId;
};


/** 
 * Parent dummy to model my data scructure
 */
struct Parent 
{
    /**
     * Child object that *should* reference (this) parent through a pointer
     */
    Child child;

    /**
     * Some parent field for demonstration
     */
    const char * someParentData;

    /**
     * What's our name?
     */
    int uniqueId;


    Parent() 
    { 
        uniqueId = std::rand();

        // Luke, I'm your father!
        child.parent = this;
        child.originalParentUniqueId = uniqueId;

        // We'll be GLaD we get burned (somewhere inside std::map)
        someParentData = "The cake is a lie.";

        // Our child will be adopted by another Parent, but he will always remember us.
        // (by keeping that child.parent ptr pointing at THIS instance)
    }
};


//
// Test case
//


#include <map>
#include <ctime>
#include <iostream>


typedef std::map<int, Parent> test_map_t;

int _tmain(int argc, _TCHAR* argv[])
{
    std::srand( std::time( NULL ) );


    //
    // Testing without std::map first.
    //

    Parent testParent;

    if( testParent.child.parent != &testParent )
    {
        std::cout << "The pointers do NOT match. Impossiburu!\n"; // can't get here
    }
    else
        std::cout << "The pointers match. Things work as expected.\n";

    std::cout << "\n";


    //
    // Let's test std::map now
    //

    test_map_t testMap;

    Parent parent = testMap[ 42 ]; // life, the universe and everything...

    if( parent.child.parent != &parent )
    {
        std::cout << "The pointers do NOT match.\nMight crash in case of access violation...\n";
        std::cin.get();
    }
    else
        std::cout << "The pointers match. Houston, we have a problem.\n"; // can't get here

    std::cout 
        << "parent.uniqueId: \"" 
        << parent.uniqueId << "\"\n"
        << "parent.child.originalParentUniqueId: \"" 
        << parent.child.originalParentUniqueId << "\"\n\n"
    ;

    std::cout 
        << "parent.someParentData: \"" 
        << parent.someParentData << "\"\n"
        << "parent.child.getSomeParentData(): \"" 
        << parent.child.parent->someParentData << "\"\n"
    ;

    std::cin.get();

    return 0;
}

Output:

The pointers match. Things work as expected.

The pointers do NOT match.
Might crash in case of access violation...

parent.uniqueId: "1234321"
parent.child.originalParentUniqueId: "1234321"    <- Match.

parent.someParentData: "The cake is a lie."
parent.child.getSomeParentData(): "               <- Access violation reading 
                                                     address 0xcccccccc (literally),
                                                     no further output

Access violation gets raised when accessing parent.child.parent -> someParentData. Debugging this issue on a real app showed that the Parent returned from std::map::operator[] is different from the one that was used to create a Child, but the Child is the same object that was instantiated during the original Parent's construction.

It goes like this: you call operator[], it creates parent_A, parent_A creates a child_A and sets it's child_A.parent pointer to &parent_A. Then for some reason parent_A gets destroyed, and parent_B takes it's place. But it stores the same old data including child_A, who's child_A.parent still points to parent_A.

The question is, why does it happen and how to solve this issue?

One of the project's requirements is use of vs2005 with it's native compiler.

Thank you in advance!

G. Kashtanov
  • 401
  • 3
  • 11
  • 1
    Your `Parent` class fails to implement the "Rule of 3" :http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – PaulMcKenzie Jan 29 '15 at 22:56

1 Answers1

1

When you use:

Parent parent = testMap[ 42 ];

You are getting a copy of the Parent in the map. Of course, the child in this parent object points to the Parent that does not exist.

You need to implement the copy constructor of Parent that does the right thing for the contained child.

Parent(Parent const& copy) 
{
    uniqueId = std::rand();

    child.parent = this;
    child.originalParentUniqueId = uniqueId;
}

Hmmm... not sure what can be used from copy.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Thanks for your answer! Does it mean that modifying `copy`'s fields will no affect the original Parent that is stored in the map? How to get a reference to it instead of a copy? – G. Kashtanov Jan 29 '15 at 23:05
  • 2
    You can get a reference by using `Parent& parent = testMap[ 42 ];`. – R Sahu Jan 29 '15 at 23:07