Is it possible to initialize a class' reference member inside a copy-constructor?

In the copy constructor for Person, how do I create a copy of Alice's pet and assign it to the copied Person's pet reference?

In my current implementation, assigning to the reference replaces Alice's pet with the copied pet (i.e., copiedPet). What I'd like the copy-constructor to do is create another Pet object that is independent of Alice's pet.

Perhaps based on what I want to do in the copy constructor, pet looks like it should be a Pet* rather than a Pet&. Thoughts on this?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <iostream>
#include <string>
#include <sstream>

enum class Species {Cat, Dog, Bird, Fish};

class Pet
{
public:
	std::string name;
	Species species;
	int age; 

	Pet(std::string name, int age, Species species) 
		: name(name), species(species), age(age) 
	{}

	Pet(const Pet& other)
	{
		std::cout << "Pet copy constructor called on `" << other.name << "`\n";
		name = other.name;
		species = other.species;
		age = other.age;
	}

	void AgeOneYear()
	{
		age++;
	}
};

class Person
{
public:
	std::string name;
	Pet& pet;

	Person(std::string name, Pet& pet) : name(name), pet(pet)
	{
	}

	
	Person(const Person& other) : name(other.name), pet(other.pet)
	{
		std::cout << "Person copy constructor called on `" << other.name << "`\n";
		
		// Copied Person's name prefixed with "[Copy]"
		std::ostringstream personName;
		personName << "[Copy]" << other.name;
		name = personName.str();

		// Copied Pet's name prefixed with "[Copy]"
		std::ostringstream petName;
		petName << "[Copy]" << other.pet.name;

		Pet* copiedPet = new Pet(petName.str(), other.pet.age, other.pet.species);
		pet = *copiedPet; // Error
	}
	

	~Person()
	{
		std::cout << "Calling `" << name << "` destructor!\n";
	}

	void PetAgeOneYear()
	{
		pet.AgeOneYear();
	}
};

int main(int argc, char** argv)
{
	using std::cout;
	using std::endl;

	Pet Sherlock = Pet("Sherlock", 3, Species::Dog);

	Person Alice = Person("Alice", Sherlock);
	Alice.PetAgeOneYear(); // Sherlock is now age 4
	cout << "Alice.Sherlock.age: " << Alice.pet.age << "\n";

	cout << "Copying Alice -> [Copy]Alice\n";

	Person CopyAlice = Alice; // Invoke Person copy constructor
	CopyAlice.PetAgeOneYear();
	cout << "[CopyAlice].[Copy]Sherlock.age: " << CopyAlice.pet.age << "\n";

	// Did CopyAlice.PetAgeOneYear() affect Alice's pet's age?
	cout << "Alice.Sherlock.age: " << Alice.pet.age << "\n";

	std::cin.get();
	return 0;
}
Last edited on
¿why does `Person' hold a reference to `Pet' instead of an object?
¿what's the point of lines 56--57? ¿where do you delete `copiedPet'?

¿what's the relationship that you want to represent between `Pet' and `Person'?
¿why does `Person' hold a reference to `Pet' instead of an object?


If Person had a Pet pet; member, it'd be a copy of a Pet object and not one that was already created.

¿what's the point of lines 56--57? ¿where do you delete `copiedPet'?


Nowhere. I thought about the lifetime of a copied Pet object and it doesn't make sense for a Pet object to be destroyed when a Person is destroyed, which is why I didn't delete pet in the Person's class destructor.

I don't know how to use the copy-constructor to allocate stack memory. That was going to be a follow up question: is it common practice to allocate memory from the heap in a copy constructor? If it's possible to allocate stack memory from the scope in which the copying is done (i.e., line 85), how is it done?

¿what's the relationship that you want to represent between `Pet' and `Person'?

An aggregation[1] relationship. If a Person gets destroyed, it does not destroy its Pet member.


[1] https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-aggregation-vs-composition/
Last edited on
This uses your code but simplifies it. The main difference is the age and aging of the Pet are a property and method of the Pet and not the Person. Ask yourself the question when you encapsulate each of the two x=classes "How can a Person age a Pet?", excluding some sort of anxiety() method encapsulated in the Person class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <iostream>
#include <string>
#include <sstream>

enum class Species {Cat, Dog, Bird, Fish};
class Pet
{
public:
    std::string name;
    Species species;
    int age;
    
    Pet(std::string name, int age, Species species)
    : name(name), species(species), age(age)
    {}
    
    Pet(const Pet& other)
    {
        name = other.name;
        species = other.species;
        age = other.age;
    }
    
    void AgeOneYear()
    {
        age++;
    }
};

class Person
{
public:
    std::string name;
    Pet* pet;
    
    Person(std::string name, Pet* pet) : name(name), pet(pet){}
    Person(const Person& other) : name(other.name), pet(other.pet){}
    ~Person(){}
};

int main(int argc, char** argv)
{
    
    Pet Sherlock = Pet("Sherlock", 3, Species::Dog);
    std::cout << "1a. Sherlock.age: " << Sherlock.age << '\n';
     std::cout << "1b. Sherlock.name: " << Sherlock.name << '\n';
    
    Person Alice = Person("Alice", &Sherlock);
    std::cout << "2. Sherlock.age: " << Alice.pet -> age << '\n';
    
    Alice.pet -> AgeOneYear(); // Sherlock is now age 4
    std::cout << "3a. Alice.Sherlock.age: " << Alice.pet -> age << '\n';
    std::cout << "3b. Sherlock.age: " << Sherlock.age << '\n';
    
    
    Person CopyAlice = Alice; // Invoke Person copy constructor
    std::cout << "4a. CopyAlice.Sherlock.age: " << CopyAlice.pet -> age << '\n';
    std::cout << "4b. Sherlock.age: " << Sherlock.age << '\n';
    
    Pet CopySherlock = Sherlock;
    CopySherlock.name = "Copy_of_Sherlock";
    
    Sherlock.AgeOneYear();     // 4 -> 5
    Sherlock.AgeOneYear();     // 5 -> 6
    std::cout << "5a. Sherlock.age: " << Sherlock.age << '\n';
    std::cout << "5b. Sherlock name: " << Alice.pet -> name << '\n';
    std::cout << "5c. Sherlock name: " << Sherlock.name << '\n';
    
    CopySherlock.AgeOneYear(); // 4 -> 5
    std::cout << "6. CopySherlock.age: " << CopySherlock.age << '\n';
    
    CopyAlice.pet = &CopySherlock;
    std::cout << "7. CopySherlock name: " << CopyAlice.pet -> name << '\n';
    
    std::cin.get();
    return 0;
}


1a. Sherlock.age: 3
1b. Sherlock.name: Sherlock
2. Sherlock.age: 3
3a. Alice.Sherlock.age: 4
3b. Sherlock.age: 4
4a. CopyAlice.Sherlock.age: 4
4b. Sherlock.age: 4
5a. Sherlock.age: 6
5b. Sherlock name: Sherlock
5c. Sherlock name: Sherlock
6. CopySherlock.age: 5
7. CopySherlock name: Copy_of_Sherlock

Last edited on
If Person had a Pet pet; member, it'd be a copy of a Pet object and not one that was already created.
I think you'll find that's a good thing.
it doesn't make sense for a Pet object to be destroyed when a Person is destroyed
Then when should a Pet object be destroyed?

If you create a Person on the heap, how will you guarantee that the Pet object isn't destroyed before the Person?

If you really want people and pets to be loosely coupled then you need a collection that owns the pets (meaning the collection destroys them) and a person contains an iterator into the pet collection, or a pet ID that can be used to lookup the pet.
I don't know how to use the copy-constructor to allocate stack memory.
It can't. Objects can be constructed in global memory, on the stack, on the heap, or anywhere else really. Why would objects in all of those places what to allocate memory on the stack?
The main difference is the age and aging of the Pet are a property and method of the Pet and not the Person. Ask yourself the question when you encapsulate each of the two x=classes "How can a Person age a Pet?"


Oof, you're right. It makes no sense that Person ages a pet. I apologize, you'll have to excuse the stupid examples of class encapsulation. I'll give more thought to the class names next time.


class Person
{
public:
std::string name;
Pet* pet;

Person(std::string name, Pet* pet) : name(name), pet(pet){}
Person(const Person& other) : name(other.name), pet(other.pet){}
~Person(){}
};


I see you've changed the reference to a pointer. I have considered this approach and didn't see much of a difference between having the class hold a reference or a pointer to the Pet object.

In either case, Person isn't suppose to have a copy of Pet but rather a reference to it. The point is that any Person can change the state of its Pet member (and all other Person with a reference to the same Pet will see the changes).


Then when should a Pet object be destroyed? If you create a Person on the heap, how will you guarantee that the Pet object isn't destroyed before the Person? ... If you really want people and pets to be loosely coupled then you need a collection that owns the pets (meaning the collection destroys them) and a person contains an iterator into the pet collection, or a pet ID that can be used to lookup the pet.


Those are important questions but when Pet should be destroyed and the validity of the Pet reference inside a Person object isn't the focus of my question. Their lifetime is insignificant (they're just there to demonstrate an example of a class with a reference to another class).

If Pet actually represented something useful, I agree, having a collection to manage the lifetime of all Pet objects make sense.

It can't. Objects can be constructed in global memory, on the stack, on the heap, or anywhere else really. Why would objects in all of those places what to allocate memory on the stack?


Objects can be copied anywhere. Suppose you wanted the lifetime of a copied object to be within the scope of a function:

1
2
3
4
5
6
7
int Person::CalculationInvolvingCopiedPerson(const Person& p)
{
     Person copiedPerson = p;
     // Some work being done on copiedPerson which mutates copiedPerson's members. Returns int.
     
     return calcResult;
}  


You wouldn't want members from copiedPerson (i.e., pet) to be heap allocated because then you'd have to explicitly deallocate it. copiedPerson has local scope. You'd want all the memory tied to that object to be on the stack so that when the function ends, they too can automatically be deallocated.
Last edited on
once you set a reference, you can't change to which object it refers to
so in Person(const Person& other) : name(other.name), pet(other.pet) you say `this->pet' and `other.pet' refer to the same object
later when you do pet = *copiedPet; you are modifying the state of the object (i.e., the value of its member variables)
and because `this->pet' and `other.pet' refer to the same object you see the changes on `Sherlock'

> ¿where do you delete `copiedPet'?
>> Nowhere. I thought about the lifetime of a copied Pet object
>> and it doesn't make sense for a Pet object to be destroyed
>> when a Person is destroyed
the scope of `copiedPet' is limited to the copy constructor, not to the Person this lifetime
you never delete it, you leak memory


given that in your last example you do expect that when `copiedPerson' dies it kills `copiedPerson.pet'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Pet{
public:
   Pet* clone() const{
      return new Pet(*this);
   }
};
class Person{
public:
   Person(const Person &other):
      pet(other.pet->clone())
      {}
private:
   std::shared_pointer<Pet> pet;
};
there Person::pet dies if nobody points to it, however note that pets must be allocated on the heap (perhaps you may touch the Deleter option in shared_ptr)
Last edited on
The default value for Pet* pet in Person should be nullptr. The Person class will also need get and set methods for pet in order to manage the Pet/Person coupling. That way a Person can change pets, or with further modification, have no Pet’s at all by setting pet to nullptr.

It would over complicate matters, memory leaks and dangling pointers, for Person’s to have methods such that a Person creates new Pet’s and then has to delete them.
copiedPerson has local scope. You'd want all the memory tied to that object to be on the stack so that when the function ends, they too can automatically be deallocated.
I agree... when copiedPerson is on the stack. But what about this:
1
2
3
4
5
int Person::newPerson(const Person& p)
{
     Person *copiedPerson = newPerson(p);
    return copiedPerson;
}

Do you still want the memory for the copiedPerson to be deallocated automatically now? Of course not.

If Person contains a reference to Pet then what happens when the pet dies? Or the person wants to sell the pet? Or get a new pet? You can't change a reference so you're binding the Pet to the Person for the lifetime of the Person object.
> But what about this
doesn't compile, conversion from `int' to `Person*'
also, stack overflow

no idea what you wanted to show
What I'd like the copy-constructor to do is create another Pet object that is independent of Alice's pet.


You can do it by using new in the Person copy constructor but I wish you lots of luck in keeping it simple and keeping track of the bound Pet, especially when the destructor has to kick in.

With all the complications associated with this why not just copy the name etc of the person, with the Pet pointer nullptr and 're-attach', via a Person attach() method (maybe), the new Pet created somewhere else. After all you are creating a copy of a Person?? A Person's attachment to a Pet is hardly an act of Pet creation ... ;)

doesn't compile, conversion from `int' to `Person*'
also, stack overflow

no idea what you wanted to show

Doh! Not enough coffee. I meant something like this:
1
2
3
4
5
Person* Person::newPerson(const Person& p)
{
     Person *copiedPerson = new Person(p);
    return copiedPerson;
}

The OP wanted to store the copied Pet on the stack. I was trying to demonstrate how that wouldn't work if the Pet's owner was created on the heap and returned to the caller.

Pets can outlive people.
People can outlive pets.
People can give away or sell pets to other people.

So a person should contain a pointer or some other unique identifier for the pet that they own, if they own one. The pet object itself is stored in a collection of pets.

If you copy a person who owns a pet, you should copy the pet too. Clone the person, clone the pet right?
Topic archived. No new replies allowed.