validate my understanding (polymorphism, inheritance, static class usage, heap and stack usage seperation)

Pages: 12
Hello.

This code compiles fine in VS2015 and on cpp.sh, but what I'm concerned with is my actual knowledge of what is occurring in the code. This is mainly for a real grasp of everything.

My background is in php development, and while a lot of things are similar with the two languages, a lot are different too. I wanted to make sure everything looks OK, so if something is wrong with what I am doing, please let me know here publicly and I will take no offense to criticism.

To follow my logic, I included comments.

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
// polymorphism test.cpp : Defines the entry point for the console application.
//

#include <stdio.h>

class animal
{
protected:
	char* message;
public:
	char* name;
	char* favoriteFood;
	animal()
	{
		this->message = "Hello from ";
		this->name = "SOMEANIMAL. I don't have a name yet :(";
		printf(this->message);
		printf(this->name);
		printf("\n");
	}
	virtual void eats() = 0;
	virtual ~animal()
	{
		this->message = "Goodbye from ";
		if (this->name == "SOMEANIMAL. I don't have a name yet :(")
		{
		    this->name = "SOMEANIMAL.  REALLY? NO ONE EVER GAVE ME A NAME!? :(";
		}
		printf(this->message);
		printf(this->name);
		printf("\n");
	}
};

class dog : public animal
{
public:
	void eats()
	{
		this->message = "dog bones";
		printf(this->name);
		printf(" loves: ");
		printf(this->message);
		printf("\n");
	}
};

class cat : public animal
{
public:
	void eats()
	{
		this->message = "cat food";
		printf(this->name);
		printf(" loves: ");
		printf(this->message);
		printf("\n");
	}
};

//a helper class which sets an animals name member
class animalNamer
{
public:
	static void setName(char *param, animal *obj)
	{
		obj->name = param;
		printf("I have a name now! It is: ");
		printf(obj->name);
		printf("\n");
	}
};

int main()
{
	//two animals created on the heap and their names set with the static helper
	dog *Scooby = new dog;
	animalNamer::setName("Scooby", Scooby);
	cat *Garfield = new cat;
	animalNamer::setName("Garfield", Garfield);
	//eats is a pure virtual function which must be implemented in all derived animals
	Scooby->eats();
	Garfield->eats();
	//clean up the heap
	delete Scooby;
	delete Garfield;
	//an animal created on the stack
	dog StackDog;
	StackDog.eats();
	return 0;
}

After posting this I now see that I never even used the member favoriteFood in the animal class. I'm surprised my compiler never gave me any warnings.
Primarily, what I'm concerned with are things like using the /new/ command and then /delete/ on the object itself. Is there no leaks occurring, even if this->message still contains something in memory? Or should the destructor handle deallocating it? (I am used to garbage collection).

What about the static class? Is it performing as a static class should?

What about my heap / stack understanding? Is this correct?

These are a few questions I would have.
Your helper class has nothing but a static function. It could be a standalone function. Actually, anybody could change the public members of an animal. That is not good encapsulation. A class can initialize its members with a constructor.

You do use stdio.h. That is inherited from C to C++ and one should use header cstdio. Better yet, study the stream-based C++ alternative: iostream

You only have pointers to literal string constants. For storing dynamically created text you do need a type that can store characters. The C++ has std::string.

Smart pointers (like std::unique_ptr), while perhaps not collecting garbage, are better than raw pointers.


Overall, not bad at all.

Nevertheless, here is a bit modified version of your code.
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
#include <iostream>
#include <string>
#include <memory>

class animal
{
public:
	animal( std::string who ) : name_( who )
	{
	  std::cout << "Hello from " << name() << '\n';
	}

	void eats() const
	{
	  std::cout << name() << " loves " << food() << '\n';
	}

	virtual ~animal()
	{
	  std::cout << "Goodbye from ";
          if ( name_.empty() )
           { std::cout << "SOMEANIMAL.  REALLY? NO ONE EVER GAVE ME A NAME!? :("; }
          else
          { std::cout << name_; }
	  std::cout << '\n';
	}

	std::string name() const
	{
	  if ( name_.empty() ) { return "SOMEANIMAL. I don't have a name yet :("; }
	  else { return name_; }
	}

	void name( std::string who )
 	{
          name_ = who;
          std::cout << "I have a name now! It is: ";
	  std::cout << name_ << '\n';
	}

private:
    virtual std::string food() const = 0;
    std::string name_;
};

class dog : public animal
{
	std::string food() const { return "dog bones"; }
public:
	dog( std::string who = "" ) : animal( who ) {}
};

class cat : public animal
{
	std::string food() const { return "cat food"; }
public:
	cat( std::string who ) : animal( who ) {}
};

int main()
{
	//two animals created on the heap
	auto Scooby = std::make_unique<dog>();
	Scooby->name( "Scooby" );
	auto Garfield = std::make_unique<cat>( "Garfield" );
	Scooby->eats();
	Garfield->eats();

	//an animal created on the stack
	dog StackDog;
	StackDog.eats();
	return 0;
}

Your helper class has nothing but a static function. It could be a standalone function.

While true, I was just using an example of a static class, performing some sort of operation on an object passed to it.

Actually, anybody could change the public members of an animal. That is not good encapsulation. A class can initialize its members with a constructor.


While name can be modified, message cannot. It is identified as protected. "name" in this case needs to be public, because I was planning to use the static class for setting it. (giving an excuse to use a static class for perhaps a bad example IDK).

You do use stdio.h. That is inherited from C to C++ and one should use header cstdio. Better yet, study the stream-based C++ alternative: iostream

Why? Can you explain.

You only have pointers to literal string constants. For storing dynamically created text you do need a type that can store characters. The C++ has std::string.

What is wrong with storing a string in a char array? Isn't that what the original C was intended for strings? Or is that because there is a leak? Is there a leak involved here? (Pretend main goes on, for a much longer period with perhaps several loops)?

Thank you for your help )
Last edited on
Seeing your constructor implementation looks awkward coming from php background.

is this preferred for setting a property?

1
2
3
4
5
6
7
class animal
{
public:
	animal( std::string who ) : name_( who )
	{
	  std::cout << "Hello from " << name() << '\n';
	}

or is it stylistic?

Would this be acceptable? This is what I'm used to:

1
2
3
4
5
6
7
8
9
10
class animal
{
    private:
        std::string name;
    public:
	animal(std::string name)
	{
		this->name = name;
                std::cout << "Hello from " << getName() << '\n';
	}
Last edited on
Names were stylistic, but the initialization is not.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
  T var;
public:
  Foo( const T & var )
  { // the this->var is default initialized before this line
    this->var = var; // this is a copy assignment
    // both T::T() and T::operator=( const & T ) were called
  }
};

class Bar {
  T var; // Edit: was missing
public: // Edit: was missing
  Bar( const T & var )
  : var( var ) // initialization with argument
  // only T::T( const & T ) is called
  {
  }
};


What is wrong with storing a string in a char array?

You don't have character arrays. Your pointers store locations of literal string constants, which while being arrays of chars are not variable and neither in stack nor heap.

1
2
// create array of 6 chars and initialize it with {'h', 'e', 'l', 'l', 'o', 0}
char array[] = "hello";

Plain arrays are not dynamic.
They decay to pointers, when passed as function arguments. Pointer does not know what it points to: invalid, single object, or element within array.
Arrays cannot be returned from functions.

The std::string is a clever object. In can be passed to functions by value or by reference. It can be returned from functions. It knows its size. It can be resized dynamically with intuitive/simple syntax. It will deallocate in its destructor whatever it has dynamically allocated during its lifetime.

The std::string and standard library containers manage the memory that they use, so that we don't have to. They do more work on our behalf than the plain arrays and raw pointers. The less we have to write boilerplate code, the less we make mistakes. I know, if we make mistakes, then we are sloppy regardless of what language we use.


The streams (std::cin, std::cout) are separate from stdin and stdout. We can have these I/O streams, filestreams, textstreams, ... All of them can be used similarly. We can pass a reference to a stream into a parsing function without the function knowing whether it is a typing user, a string, or a file, where the data comes from.

Streams have both formatted and unformatted methods. With *printf you can type a format and a variable that is not of the type indicated in the format. The stream's method is selected based on the type of the variable, so a mismatch will not occur.


Rather than having a "static class", one can put standalone functions into a namespace:
1
2
3
4
5
6
7
8
9
10
11
12
namespace animalNamer {
  void setName(char *param, animal *obj)
  {
    // what if either param or animal is a nullptr?
    obj->name = param;
  }
}

int main()
{
	dog *Scooby = new dog;
	animalNamer::setName("Scooby", Scooby);


A function or class can be a friend of a class. Friends can access private parts. Friendship is a very strong coupling though.
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Foo {
  T var;
public:
  Foo( const T & var )
  { // the this->var is default initialized before this line
    this->var = var; // this is a copy assignment
    // both T::T() and T::operator=( const & T ) were called
  }
};

class Bar {
  Bar( const T & var )
  : var( var ) // initialization with argument
  // only T::T( const & T ) is called
  {
  }
};


I need clarification on this, as this is a fundamental thing in php to do (Foo).

Isn't Bar initializing var member the same way?
I see the problem. PHP when you instantiate to pass arg to constructor like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class animal
{
   public function animal($name)
   {
      $this->name = $name;
   }
}

class dog extends animal
{
}
?>


and then you instantiate it with:

<?php $Scooby = new dog("Scooby"); ?>


but in C++ the syntax looks much different. you can't do something like

dog *Scooby = new dog("Scooby");

right?
Last edited on
Arrays cannot be returned from functions.


ouch! :( Did not know this. Thank you.
Did a moderator delete another question I had?

I will rephrase. This was explained to me by keskiverto:

You don't have character arrays. Your pointers store locations of literal string constants, which while being arrays of chars are not variable and neither in stack nor heap.


If this is true, explain how when

dog *Scooby = new dog;

sets

1
2
3
4
5
 animal()
	{
		this->message = "Hello from ";
//etc
}


but when

Scooby->eats();

it is now

this->message = "dog bones";

???
If someone could further clarify my most previous question it would be most appreciated :)
Consider const char* pointer = "Hello from " ;

The narrow string literal "Hello from " is an object of type 'array of 12 const char' with static storage duration (ie. they exist in memory for the entire life of the program.)
To initialise the pointer, an implicit array-to-pointer conversion is performed; after initialisation, the pointer points to the first character ('H') in the array.

Now consider pointer = "dog bones" ;
The narrow string literal "dog bones" is an object of type 'array of 10 const char' with static storage duration.
To assign to the pointer, an implicit array-to-pointer conversion is performed; after the assignment, the pointer points to the first character ('d') in this second array.

Array-to-pointer decay
There is an implicit conversion from lvalues and rvalues of array type to rvalues of pointer type: it constructs a pointer to the first element of an array. This conversion is used whenever arrays appear in context where arrays are not expected, but pointers are.
http://en.cppreference.com/w/cpp/language/array#Array_to_pointer_decay
but in C++ the syntax looks much different. you can't do something like
dog *Scooby = new dog("Scooby");
right?

Yes, you can. See: http://www.informit.com/articles/article.aspx?p=1852519


Class initialization. Oh dear, I had two lines missing from the code.

C++ class can have multiple constructors, overloaded functions. The default constructor takes no arguments and supplies "default values" for all members. Not all classes have default constructor.

In the syntax of definition of a constructor:
1
2
3
4
5
Foo::Foo( /* arguments */ )
 : /*initializer list*/
{
  /* body of constructor */
}

* Different constructors of Foo differ by what arguments they take.

* All the members (both base classes and member variables) are initialized before the body of constructor is executed.

* If you do need non-default initialization of a member, it has to be done within the initializer list.


Lets be more clear about "Arrays cannot be returned from functions":

A function takes as an argument either by value or by reference. (C does not have references.)
In the by value the value is copied from caller's variable into functions (argument) variable.
In the by reference the functions argument is a reference rather than a variable.
Either way, on function call, the argument is initialized from caller's data (either copy construction or creation of a reference).

Arrays are not copied. You can have a "reference to array" argument type, but that is technique rarely seen. The usual array argument style inherits from C.

Developers of C decided not to copy, put to pass the address of array instead. A pointer. Only the address, no size. Back then copying large arrays would (1) create memory hogging copies, (2) take time, and (3) complicate the compiler. It was left to the programmer to do something smart.
1
2
3
4
5
6
7
8
9
void bar( int * array, int size );

int main() {
  const int N = 5;
  int arr[N];
  // code
  bar( arr, N );
  // code
}

However, to make intent more apparent in the code, the authors of C added an alternate parameter syntax:
1
2
3
void bar( int [] array );
// is same as
void bar( int * array );

When you use the bracketed-style you hint that this function wishes to get an array rather than any pointer.


The return value of a function is again either a value (copy) or a reference. Just like in the function arguments, there is no automatic copy of every element of an array, so there is no way to get a copy of array. You can return a pointer or reference, but both of those are cause havoc, if they point to function local (stack) variable, which ceases to exists at the end of the function's scope.


Now enter the user-defined types with copy constructors. Those types are "objects". Their copy constructor can contain code to copy values from source objects array to destination objects array. The custom type can encapsulate array and perform the operations that base language does not do for plain arrays. The std::string, std::array, std::vector, etc are such custom types. They can be passed to and returned from functions by value.

Passing an object by reference to function avoids unnecessary copying, and if the reference is also const, then the function cannot modify its argument (i.e. caller's variable).



The Scoopy->message is a pointer. It stores an address. The "Hello from " and "dog bones" are constant string literals. Both have some address. All those characters are stored somewhere in the executable and in memory during runtime, but neither in stack nor in heap.

Your code changes the address stored in Scoopy->message.
The Scoopy->message is a pointer. It stores an address. The "Hello from " and "dog bones" are constant string literals. Both have some address. All those characters are stored somewhere in the executable and in memory during runtime, but neither in stack nor in heap.

Your code changes the address stored in Scoopy->message.


Ok So I am changing the address which is stored in the object (Scooby) property. I get this, but what I still don't get is how that value isn't stored anywhere in memory. Are objects properties not treated as storing something on the stack or heap?

I was under the impression that:

1
2
3
4
5
Someclass someobject;  
// is an example of creating an object on the stack of Someclass type, which, after the current functions scope ends, this object becomes null or 0.

Someclass *someobject = new Someclass;
// is an example of creating an object on the heap of Someclass type, which will continue to exist even after the current functions scope ends until all processes end. 
Thank you also about array passing. If I inject an array into a function parameter and perform operations on the array within the function, I can assume that the function will modify the array's contents, even after the function has ended, and that it is not a copy of the passed in array.

Hypothetically, say I needed a function to generate an array-ish container, but not an actual array type. Say a hashmap, or keyvalue pair, such as something generated from a MySQL query that would return the contents of two columns of data, what data type to store and return them from the function would be best?
Last edited on
> I still don't get is how that value isn't stored anywhere in memory.

A string literal is stored somewhere; it is an array with static storage duration.
The storage for it exists in memory for the entire life of the program.


> Are objects properties not treated as storing something on the stack or heap?

The C++ standard does not mention either stack storage or heap storage; instead it uses the abstract concept of a 'storage duration' - static storage duration, automatic storage duration, dynamic storage duration and in C++11, thread storage duration. (It also clearly specifies what the life-time of temporary objects should be.) In simple terms, the standard is only concerned about the lifetime of objects and the duration for which the storage for an object would be available.

A conforming implementation can store all objects in the program on a run-time stack, or on several different stacks, or on one heap, or many arenas (which may contain individual heaps of their own), or in a memory mapped disk file, or anywhere else that it pleases as long as it ensures that the lifetime (and storage) of objects conform to the storage durations as specified by the standard.

Storage duration: http://en.cppreference.com/w/cpp/language/storage_duration
See:
http://www.gotw.ca/gotw/009.htm
http://en.cppreference.com/w/cpp/language/aggregate_initialization
1
2
3
4
5
{
  char foo[] = "hello";
  char * bar = "world";
  char * gaz = new char {'x'};
}

The "hello" and "world" are in Const Data memory area. They did exist before everything else and will continue to exist to the end of the program.

The foo is a char array in Stack memory area. It is initialized by copying values from "hello".

The bar and gaz are a pointers in Stack area.

The character x is in one-byte block of Free Store memory area.

The foo, bar and gaz cease to exist at the end of the scope.

The x remains until deallocated. If you lose all pointers that had the address of x (before deallocation), then you have a memory leak.

If the scope is a function, you can return bar or gaz, because the addresses hold by them do not point to stack that will unwind at the end of the function.


What a SQL query returns ... there are multiple options. For example:
1
2
3
4
5
6
7
8
9
10
11
std::vector<std::pair<int,double>> foo() {
  std::vector<std::pair<int,double>> list;
  // append query results into the list
  return list;
}

std::map<int,double> bar() {
  std::map<int,double> list;
  // append query results into the list
  return list;
}

See http://www.cplusplus.com/reference/stl/
Each container has its unique features. How you do get the data, what is in the data, and how you use the data do determine which container is the most efficient.
Wrong. Arrays will be converted to pointers before they return.

You just proved him right. If it is being converted to a pointer before return then the function would be returning a pointer, not the array.
Thank you all for clarification. I think this helped me greatly. If there are any other further comments please feel to append them.
Pages: 12