Base* pointer points to "new" derived class

Pages: 12
This chap in the book deals with polymorphism & I have commented out some of the lines from the book & modified it to see if it will work & it seems to work just fine. I have 3 questions here:

1) In book within the Fish* Clone functions has "return new Tuna(*this);" & I changed it to "return new Tuna;" because I thought why wouldn't that work & it seems to. There is no difference between the two & probably the book just used (*this) just to show us that you can? It just uses (*this) & no further explanation.

this->myNum; //Means a pointer address to the object
(*this).myNum; //Means a REFERENCE address (by way of dereference) of the object & is to be treated
just like it was an object, needing the dot operator??????

2) Next one is a bit more troubling & perhaps yet another thing that I just have to accept? The "Fish* Clone()" function uses a Fish* base pointer to store "new" memory address pointer to a derived class? So after the "new" creation the Fish* is a pointer address, which contains the address of new Tuna derived memory location? And all references made to Fish base from this are recognized & deciphered from the Tuna pointed address...because Tuna inherits from Fish???
This is a little more troubling to wrap my head around the background workings, but I understand that it just works. On the other hand it seems if you know how it works, it is a pretty powerful & quicker way to create new memory for classes.

3)For the delete, is there a quicker/faster way to just delete the Fish* pointer & have it delete all the "new" or is this another one of those rules that supplants the need to have it spelled out a "delete" for every "new" & no way around it?

delete myFishes[index];
delete myNewFishes[index];


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
  #include <iostream>
using namespace std;

class Fish
{
public:
	virtual Fish* Clone() = 0;
	virtual void Swim() = 0;
	virtual ~Fish(){};	
};

class Tuna: public Fish
{
public:
	Fish* Clone() override
	{
		//return new Tuna(*this);
		return new Tuna;
	}
	
	void Swim() override final
	{
		cout << "Tuna swims fast in salt water" << endl;
	}
};

class BluefinTuna final: public Tuna
{
public:
	Fish* Clone() override
	{
		
		//return new BluefinTuna(*this);
		return new BluefinTuna;
	}
};

class Carp final: public Fish
{
public:
	Fish* Clone() override
	{
		//return new Carp(*this);
		return new Carp;
	}
	
	void Swim() override final
	{
		cout << "Carp swims slow in fresh water" << endl;
	}
};

int main()
{
	const int ARRAY_SIZE = 4;
	Fish* myFishes[ARRAY_SIZE] = {NULL};
	myFishes[0] = new Tuna();
	myFishes[1] = new Carp();
	myFishes[2] = new BluefinTuna();
	myFishes[3] = new Carp();
	
	Fish* myNewFishes[ARRAY_SIZE];
	for (int index=0; index < ARRAY_SIZE; ++index)
	{
		myNewFishes[index] = myFishes[index]->Clone();
		myNewFishes[index]->Swim();
		delete myFishes[index];
		delete myNewFishes[index];
	}
	
//	for (int index=0; index < ARRAY_SIZE; ++index)
//	{
//		myNewFishes[index]->Swim();
//	}
	
//	for (int index=0; index < ARRAY_SIZE; ++index)
//	{
//		delete myFishes[index];
//		delete myNewFishes[index];
//	}
	
	return 0;
}
Last edited on
1) The change is fine, as long as you understand that a polymorphic clone() member function is supposed to return a pointer to the base class that points to an instance of the derived class whose value is a copy of the this object.

2) You're not really asking a question here.

3) Yes, you can use smart pointers.
1
2
3
4
5
6
7
{
    std::vector<std::unique_ptr<Fish>> fishes;
    fishes.emplace_back(std::make_unique<Tuna>());
    fishes.emplace_back(std::make_unique<Carp>());
    //etc.
}
//fishes has gone out of scope and all associated memory has been cleanly released. 
the change is unnecessarily frail, adding a member variable will break it.
Since there's no explicitly defined copy constructor, adding a new member could still break the original code, depending on the type of that member.
Last edited on
3. For the delete, is there a quicker/faster way to just delete the Fish* pointer & have it delete all the "new" or is this another one of those rules that supplants the need to have it spelled out a "delete" for every "new" & no way around it?


There is no way (at least simple one), because "dynamic memory" (the one allocated by new) is not contiguous, one fish will be allocated in one place in memory and another fish will be allocated somewhere else but not immediately next to first fish.

That's the reason why you can't bulk delete objects allocated by new.

But it is doable by writing custom allocation functions for each of the derived classes which would allocate memory from the memory pool (a contiguous preallocated block of memory)

You then don't need to call delete at all until program exit, and allocation\deallocation is much faster too.

Memory pools are advanced topics, for more information see:
https://en.cppreference.com/w/cpp/memory/new/operator_new
http://dmitrysoshnikov.com/compilers/writing-a-pool-allocator/
Last edited on
1. return new Tuna(*this); and return new Tuna; are semantically different. The former invokes the copy constructor, the latter invokes the default constructor.

For this specific example, sure, there's no practical difference as neither Fish nor any of its subclasses have any member variables, default constructors that might cause side effects, or copy constructors that might cause side effects. However, if any of the classes had any of those, there definitely would be a practical difference, and using return new OneOfTheFishTypes; would be a mistake.

If you already understand this, great!

2. Simplified summary of how this black magic typically works: if a class has virtual functions, then the compiler adds some additional pointers (usually one pointer at the start of the class) that point to data structures (called "vtables", also generated by the compiler) that allow virtual functions to be called correctly.

So even though Fish doesn't have any member variables declared, it still contains some data. You can confirm this using sizeof(Fish).

3. I would really like to emphasize what helios said. Smart pointers (like std::unique_ptr) make C++ much safer to use in practice, in large part because you don't have to think about that delete.

helios's example uses std::vector (which is also worth using for unrelated reasons), but you can adapt the book example that uses arrays as well.

An overview of each standard library smart pointer can be found here: https://www.internalpointers.com/post/beginner-s-look-smart-pointers-modern-c

For your particular problem, knowing about unique_ptr's reset member function may also be useful:
https://www.cplusplus.com/reference/memory/unique_ptr/reset/

-Albatross
RE: using smart pointers......

helios used std::vector to hold a number of unique_ptr pointers. It is also possible to create an array of unique_ptrs directly without using another container.
https://en.cppreference.com/w/cpp/memory/unique_ptr
https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_at
This chap in the book

Which book, which edition?

I recognize the use of class Fish/Tuna/Carp, I own the 7th edition of "Sams Teach Yourself C++ in One Hour a Day" by Siddharha Rao. Covers C++ up to C++11.

The 8th edition covers C++14 and C++17.

Furry Guy: That doesn't work here, because the objects are polymorphic (and the base class is abstract anyway). You can't have an array of Fish, you need an array of pointers to Fish.
Well, I admit I haven't used a unique_ptr array much, never with polymorphic types.

I very rarely use polymorphic classes anyway, if I do any inheritance I use an inherited class directly.

Learn something new every day.
@Furry Guy:
When you say "array of unique_ptrs", which of the following better matches what you're referring to?

std::unique_ptr<Fish> arr[]

std::unique_ptr<Fish[]> arr

I ask because I'm pretty sure you mean option 1, but the link to operator[] makes no sense unless you meant option 2.

-Albatross
Oh! It could be std::unique_ptr<std::unique_ptr<Fish>[]>. Bit of a mouthful, but it would work.
I did mean option 2, Albatross, not even remotely thinking about helios' idea of nesting.

Which only shows how little I know about how to use smart pointers, and so don't run across ways to actually make things work in code.
> Since there's no explicitly defined copy constructor, adding a new member
> could still break the original code, depending on the type of that member.
if you add a non trivially copyable member, then you ought to create the copy constructor, assignment operator and destructor
if there is a custom copy constructor, then you ought to update it for variable member changes (as well as the assignment operator and destructor)

but this thing here would break on a freaking int just because the intern wanted to be a hacker.
don't encourage bad code.
Last edited on
Using smart pointer for the original code perhaps:

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
#include <iostream>
#include <vector>
#include <memory>
using namespace std;

class Fish
{
public:
	virtual Fish* Clone() = 0;
	virtual void Swim() = 0;
	virtual ~Fish() {};
};

class Tuna : public Fish
{
public:
	Fish* Clone() override
	{
		//return new Tuna(*this);
		return new Tuna;
	}

	void Swim() override final
	{
		cout << "Tuna swims fast in salt water" << endl;
	}
};

class BluefinTuna final : public Tuna
{
public:
	Fish* Clone() override
	{

		//return new BluefinTuna(*this);
		return new BluefinTuna;
	}
};

class Carp final : public Fish
{
public:
	Fish* Clone() override
	{
		//return new Carp(*this);
		return new Carp;
	}

	void Swim() override final
	{
		cout << "Carp swims slow in fresh water" << endl;
	}
};

int main()
{
	vector<unique_ptr<Fish>>myFishes;

	myFishes.emplace_back(new Tuna());
	myFishes.emplace_back(new Carp());
	myFishes.emplace_back(new BluefinTuna());
	myFishes.emplace_back(new Carp());

	vector<unique_ptr<Fish>> myNewFishes;

	for (const auto& f : myFishes) {
		myNewFishes.emplace_back(f->Clone());
		myNewFishes.back()->Swim();
	}
}



Tuna swims fast in salt water
Carp swims slow in fresh water
Tuna swims fast in salt water
Carp swims slow in fresh water

if you add a non trivially copyable member, then you ought to create the copy constructor, assignment operator and destructor
if there is a custom copy constructor, then you ought to update it for variable member changes (as well as the assignment operator and destructor)

but this thing here would break on a freaking int just because the intern wanted to be a hacker.
don't encourage bad code.
The code as it is works, whether it calls the copy constructor or not. Either is equivalent with the code as-is. Yes, if the code changes the clone() implementation could be left invalid, but if the code changes other parts of the code could also be left invalid, depending on the specific change. Whether one thing is invalidated sooner than another is, IMO, not very relevant. OP is barely trying to get their head around polymorphism.

I'm not encouraging bad code. The code was bad from the start. It's a bad example that's simultaneously too reductive and contains irrelevant unexplained details.
I'm not encouraging bad code. The code was bad from the start. It's a bad example that's simultaneously too reductive and contains irrelevant unexplained details.

That's a "Sams Learn to Program C/C++" book for ya. I know, I own several, and have used them to self-teach from.

Before I found Learn C++ and CPlusPlus.

The section of the ISOCPP faq detailing how to learn C++ listing multiple books is also helpful, if a bit outdated.
https://isocpp.org/wiki/faq/how-to-learn-cpp
It's okay, I originally learned from "Teach Yourself C++ in 21 days", the edition from before standardization. If you don't know anything, it's better than nothing.
Thanks for everyone's replies, you guys are awesome!

@Furry Guy
Yes, Sam's 8 Ed.. I zipped through the first 8 chapt & then came to a crawl on Classes & just started operators yesterday. I knew of variables, for loops, do....etc from my dabbling in VB and so that part was easy enough. The book is a great introduction, but every time I turn around there are new tidbits beyond the book. I wonder if I should have just started on a new full-scale book, maybe Bjarne Stroustrup's "The C++ Programming Language, 4th Ed"..any recommendations? I would be more interested in a book that spells things out, so I can clearly understand the topics & not one that makes assumptions. I am 7 weeks into my start with C++ & I will beat it into me!

@Helios
From the book the scope resolution operator, dot, & () when referring to classes & their members frustrated the hell out of me. Until Helios made that mini program showing the differences, that was very helpful & thanks Helios! And hearing you say the next quote...reiterates that this is just known & it is just the way it is...as opposed to experienced programmers would never do that & there is a better way.

1) The change is fine, as long as you understand that a polymorphic clone() member function is supposed to return a pointer to the base class that points to an instance of the derived class whose value is a copy of the this object.


@Albatross
1. return new Tuna(*this); and return new Tuna; are semantically different. The former invokes the copy constructor, the latter invokes the default constructor.


Ah yes, that is exactly the insight I needed as I thought they were exactly the same...makes sense when I look at it again now.

return new Tuna; //Creates a new Tuna with no regard to "this" Tuna, but they just happen to be the same. But if we added a single int num; variable here with a copy constructor & assignment operator, then it is a big mistake.

return new Tuna(*this); //This has the potential to copy entirely this Tuna. I have to be honest that I am disturbed by (*this) usage as I would have tried this->Tuna or (*this).Tuna before even thinking the above syntax was possible.
______________
The book mentions smart pointers & I saw the first line in the operator chap yesterday coincidentally. Good to know they exist, will cover them in future chap.

Book also gave example on a char* buffer pointer passed to a global function that needs to ensure a deep copy via a copy constructor. So when you copy a pointer, it makes a new pointer address location, stores the address of the pointer being copied (a copy of it), but does not store the value of what is pointed to...for instance the int. And this applies to all attributes? Did I get that right? I also think the author made the code simpler for understanding, which I am glad he did...for an intro.

Last edited on
any recommendations? I would be more interested in a book that spells things out, so I can clearly understand the topics & not one that makes assumptions.

Not a book, but "Learn C++" is a good online tutorial site.
https://www.learncpp.com/

Can ask questions at the site.

Using just one book can be a bit hard to learn from, there are a lot to learn about C++. Multiple books certainly broaden the scope.
https://isocpp.org/wiki/faq/how-to-learn-cpp#buy-several-books

Leanpub has eBooks on C/C++, anything written by Nicolai M. Josuttis or Rainer Grimm would be a book to consider buying. C++11 created "modern" C++, C++17 and C++20 made some pretty hefty changes and additions to the standard.

None of the books at Leanpub are for-beginners books IMO. They are useful when you've got some experience in your programming toolbox.
Pages: 12