Difference creating instances of class using pointer

what is the difference and concept between:
1. base* foo1 = new base;
2. base* foo2 = new derived;
3. derived* foo4 = new base; (does not work, but why?)
4. derived* foo3 = new derived;

what do "new base" and "new derived" do?
why we want to create an instance of object on heap space but not stack?

I tried to find an answer for this, but I did not find it.
my professor told me to read virtual function, but I don't see the connection between my question and the virtual function.

Thank you.

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
class base
{
public:
	base()
	{
		cout << "inside of base constructor" << endl;
	}
	~base()
	{
		cout << "Inside of base destrcutor" << endl;
	}
};

class derived :public base
{
public:
	derived()
	{
		cout << "Inside of derived constrcutor" << endl;
	}
	~derived()
	{
		cout << "Inside of derived destrcutor" << endl;
	}
	
};


Last edited on
In C++, inheritance is an is-a relationship. In other words, if Derived inherits from Base, then a Derived "is a" Base. This is a one-way relationship: although a Derived "is a" Base, a Base may not "be a" Derived. For example, all dogs are mammals, but some mammals are not dogs.
If it was possible to do this:
 
Dog *dog = new Mammal; //implicit cast from Mammal * to Dog * 
then it would be possible to do this:
 
Dog *dog = new Cat; //Cat * -> Mammal * -> Dog * 
and also this:
1
2
3
4
5
void f(Dog *dog){
    dog->bark();
}

f(new Cat);
This would be a problem, since Cats don't know how to bark().

The inverse conversion, from Dog * to Mammal * and from Cat * to Mammal *, is allowed because Dogs have, by virtue of being Mammals, all the capabilities all Mammals have. For example:
1
2
3
4
5
6
7
8
9
10
void g(Mammal *m){
    m->grow_hair(); //all Mammals have hair
    if (m->gender() != Female) //all Mammals have gender
        return;
    m->lactate(); //all female Mammals can lactate
}

g(new Dog); //valid
g(new Cat); //valid
g(new Chicken); //invalid 


In other words, inheriting from a class means inheriting the base class' interface. The interface of a derived class is a non-strict superset of the interfaces of each of its base classes.
Last edited on
Thank you for answering me, I got it.

However, what do
1
2
base* foo1 = new base;
base* foo2 = new derived;


I don't know what the "new" does in this situation.
I only know those "new base" & "new derived" create in heap space.
For base* foo2 = new derived;, it can not use methods/functions in derived class.
what is the point to have "new derived"? I can not use methods in derived class.
why don't use write derived* foo2 = new derived;, so I can use methods/functions in both base and derived classes?

Thank you.
Last edited on
For base* foo2 = new derived;, it can not use methods/functions in derived class.


It would if they were virtual functions. In the code below, we have a pointer to Base, but the function that gets called is Derived::speak


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Base
{
    public:
    virtual void speak(){std::cout << "I am base" << '\n';}
};

class Derived: public Base
{
    public:
    void speak(){std::cout << "I am derived" << '\n';}
};

int main()
{
    Base* pointer_to_base = new Derived;
    
    pointer_to_base->speak();
}

I don't know what the "new" does in this situation.
I only know those "new base" & "new derived" create in heap space.
I don't follow. First you say you don't know what new does and then you say exactly what it does.

what is the point to have "new derived"? I can not use methods in derived class.
The point is that 1) you can write generic functions that operate on data without knowing the specifics of that data, and 2) that you can design heterogeneous data structures that mix and match different types of data.
As an example of #1, consider 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
class Mammal{
public:
    virtual void speak() = 0;
};

class Dog : public Mammal{
public:
    void speak() override{
        std::cout << "woof!\n";
    }
};

class Cat : public Mammal{
public:
    void speak() override{
        std::cout << "meow!\n";
    }
};

Mammal *create(){
    return rand() % 2 == 0 ? new Cat : new Dog;
}

int main(){
    Mammal *unknown_mammal = create();
    unknown_mammal->speak();
}
The type of the object pointed to by unknown_mammal is unknowable until the program is actually running, but polymorphism allows us to write main() so that it can use the object's functions without knowing anything about it, other than it's a mammal.

why don't use write derived* foo2 = new derived;, so I can use methods/functions in both base and derived classes?
Polymorphism is a tool. The point of having it is so you can use it when it helps in solving a problem. Yes, in your examples above you can write derived* foo2 = new derived; instead of base* foo2 = new derived; and it won't make any difference.
However, your examples were chosen by yourself. You didn't have any design constraints when you wrote them. It would be like if you asked "what's the point of airplanes, when I can reach all the stuff in my house with a ladder?"
Try writing main() from my example without upcasting to Mammal *.

EDIT:
Here's an example of a heterogeneous data structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class DirectoryElement{
public:
    virtual int size() = 0;
};

class Directory : public DirectoryElement{
    std::vector<DirectoryElement *> children;
public:
    int size() override{
        return this->children.size();
    }
};

class File : public DirectoryElement{
    int file_size;
public:
    int size() override{
        return this->file_size();
    }
};
Note that a Directory may contain both Files and other Directories as children, while at the same time querying the size() of an element will return something different depending on whether the element is a File or a Directory.
Last edited on
1. base* foo1 = new base; Create a base object on the heap and assign its address to foo1.
2. base* foo2 = new derived;Create a derived object on the heap. Convert the pointer-to-derived into a pointer-to-base and assign that to foo2. This is legal. The pointer-to-base points to the base object embedded within the derived object.
3. derived* foo4 = new base; Although you can cast from derived to base, you can't go the other way because, as this example shows, there's no guarantee that there actually IS a derived object associated with the base object.
4. derived* foo3 = new derived; Create a new derived object on the heap and assign it's address to foo3.

why we want to create an instance of object on heap space but not stack?

When creating objects on the stack, you have to know how many to create at compile time. When we only know the number at runtime, we put them on the heap (and link them together somehow, such as with a list or in a vector).
why we want to create an instance of object on heap space but not stack?

There are at least three reasons:

1. Space. The stack is small compared to the heap. If you have much data, then fitting it into the stack is an issue.

2. Dynamical needs. Data does often come from the user during runtime. You cannot possibly know, how much, when you write the code. The heap allows dynamic allocation.

3. Lifetime of an object. Objects in stack live only to the end of their current scope. Objects in heap are not limited to a scope.


base* foo2 = new derived;
I don't know what the "new" does in this situation.

Does it confuse you that there are two "situations" in that one statement?

There is an expression new derived. Expressions return a value.
This expression returns a memory address.
It also allocates a block of memory and constructs an object of type derived within that block.
The returned address is the address of that block.
That is one "situation". Just like you said:
create in heap space.


There is a second "situation" too.
An object is created in stack space. That object is initialized with a value.
The object has type base*. The object is a pointer.
The value is what the expression on the right returns.

That is no different from int bar = 42; where an integer object is created on stack and initialized with the value that the expression 42 returns.
Thank you for answering my questions.
took me a while to understand.

last question:
1
2
3
base *foo1;
derived foo2;
foo1 = &foo2;

for this one, it is same as base* foo1 = new derived? instead I am creating in heap, I creating in stack.
Last edited on
Yes. In your example, you created the derived object on the stack instead of the heap.
In both cases the foo1 is an object in stack that (eventually) stores an address of an another object.

In one case the address is of an object that has name "foo2" and is in stack.
In second case the address is of an object that has name no name and is in heap.


PS. Do not forget to appropriately deallocate the dynamically allocated memory.
Thank you very much.
Topic archived. No new replies allowed.