does polymorphism work with objects that were created statically ?


I am still trying to get a better understanding of how polymorphism works in C++. Do i need to create the objects dynamically with the new keyword in order to make polymorphism work ?  


lets say i create these objects statically:

Circle c1(1, 1, 1);

Circle c2(2, 1, 1);

Line l4(1, 1, 2, 2);


and then push them as a shared pointer in a vector ?

v.push_back(make_shared <Line>(l2));
v.push_back(make_shared <Line>(l1));
v.push_back(make_shared <Circle>(c2));


does that make the objects become dynamic because I have used make_shared ?



i went on cppreference http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared

and there is a template like this : 

std::shared_ptr<T>(new T(args...))


So can we do something like this :

v.push_back(make_shared<Circle>(new Circle(1, 2, 3)));


which way is better ?

Do i need to create the objects dynamically with the new keyword in order to make polymorphism work ?


No, it works with raw pointers on the stack, lvalue references, and smart pointers. Avoid using new in all cases, because manual memory management can be avoided. The problem with new is that if something throws an exception, delete and the destructor is never reached, so it leaks memory.

does that make the objects become dynamic because I have used make_shared ?


Yes, make_shared uses new internally, so it's memory is dynamically allocated on the heap.

and there is a template like this :

std::shared_ptr<T>(new T(args...))

That is a clue as to how shared pointer is implemented, you don't have to copy that in your code.

So v.push_back(make_shared <Line>(l2)); is fine for now, except:

* It probably should be unique_ptr (where is the shared ownership?), use std::move because they can't be copied
* there is also emplace_back which constructs the object in place
* as mentioned in another post, one can have a container of std::reference_wrapper

In the example here:

http://en.cppreference.com/w/cpp/container/vector/emplace_back

A move constructor is defined.


http://en.cppreference.com/w/cpp/utility/functional/reference_wrapper
what is this template mean :

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

from http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared


how do I call this template function and will T or Args be constructed using the "new" keyword ?
how do I call this template function and will T or Args be constructed using the "new" keyword ?
The class T will be constructed using new. args are the parameter provided to the constructor. That means a constructor is required that takes the provided args otherwise you get an [obscure] compiler error.

So can we do something like this :

v.push_back(make_shared<Circle>(new Circle(1, 2, 3)));
No.

Either

v.push_back(make_shared<Circle>(new Circle(1, 2, 3)));

or

v.push_back(shared_ptr<Circle>(new Circle(1, 2, 3)));

or

v.push_back(new Circle(1, 2, 3));
what is this template mean :

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

from http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared


Again, that is how a compiler might implement the function. It gives clues about how to call the function, but it could be too advanced at the moment. Instead just look at the examples.

We could attempt explain templates, variadic functions, forwarding references but it would take too long.

Have you read this yet ?
http://www.cplusplus.com/doc/tutorial/polymorphism/

There are a couple of things I don't like about it: Use of new, and protected data members. You could try with raw pointers to start with, then try references with std::reference_wrapper, then smart pointers.
masterinex wrote:
Do i need to create the objects dynamically with the new keyword in order to make polymorphism work ?
lets say i create these objects statically:
Circle c1(1, 1, 1);

Using "dynamic" vs "static" this way is not quite how these words are used in C++ literature and programmer communication. There is nothing "static" about creating an object this way.

dynamic polymorphism takes place when static type of an expression differs from the dynamic type of the object that expression refers to, and you make a virtual member function call:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <cmath>
struct Shape {
    virtual double area() const = 0;
    virtual ~Shape() = default;
};
struct Circle : Shape { 
    double x,y,r;
    Circle(double x, double y, double r) : x(x), y(y), r(r) {}
    double area() const override { return std::acos(-1)*r*r; }
};
void demo(const Shape& s) {
    std::cout << s.area(); // <- here is the runtime-polymorphic code
}
int main()
{
    Circle c1(1, 1, 1);
    demo(c1);
}

live: https://wandbox.org/permlink/JsSrvUGKZ74kfiph

in this demo, dynamic polymorphism takes place inside the function demo, when the expression "s.area()" is executed.
The static type of "s" in that function is "Shape", the dynamic type of "s" is "Circle", so the call to s.area() chooses, at run time, Circle::area instead.
If I then do
1
2
Line l4(1, 1, 2, 2);
demo(l4);

, the very same function "demo", at the very same line s.area(), will call a different function, Line::area. This is what "polymorphism" means: same code does different things.

Everything else - pointers, containers, etc - is just added layers on top of this core concept.

(and yes, don't use shared pointers if you're not sharing ownership between multiple threads or otherwise unpredictably terminating owners. Use std::unique_ptr if you need a container that owns a bunch of heap-allocated objects of different type with a common base:
1
2
3
    std::vector<std::unique_ptr<Shape>> v;
    v.push_back(std::make_unique<Circle>(1,1,1));
    v.push_back(std::make_unique<Line>(1,1,2,2));

that alone is not an example of runtime polymorphism though: it will only happen when you make a call to a Shape's virtual member function, perhaps like this:
1
2
    for(const auto& p : v)
        std::cout << p->area(); // polymorphic call here 

)
Last edited on


in this demo, dynamic polymorphism takes place inside the function demo, when the expression "s.area()" is executed.
The static type of "s" in that function is "Shape", the dynamic type of "s" is "Circle", so the call to s.area() chooses, at run time, Circle::area instead.
If I then do




does that mean that even though c1 was not dynamically created, the call demo(c1); is dynamic it chooses the correct function Circle::Area, because you are passing c1 as a reference of Shape.

Then why is that I always find that objects are created dynamically with the "new" keyword in conjunction with Polymorphism ?
I have rewritten your Shape example , a little bit , by using a Constructor for Shape.
When the constructor of Circle is called , it calls the constructor of Shape. I'm curious about this, how is it possible for us to call the constructor of Shape ,even though now Shape is abstract ?



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

// this example shows that  we do not need to create the objects dynamically for 
// polymorphism to work , we either pass the object by reference or by pointer to a derived class such as Cirlce
//however if we want to avoid the side effect such as slicing
// we need to create the objects dynamically with the new keyword or by using make_shared a shared pointer
// to get slurping to work

#include <iostream>
#include <cmath>

#define PI 3.14

using namespace std;

struct Shape {

	double x_;
	double y_;

	Shape(double x, double y) : x_(x), y_(y) {} // even though Shape itself is abstract, we can still use the constructor of Shape

	virtual double area() const = 0;  // make area pure virtual

	virtual ~Shape() = default;

	void getlocation()
	{
		cout << " x: " << x_ << "y: " << y_ << endl;
	}


};
struct Circle : Shape {
	double x, y, r;
	Circle(double x, double y, double r) : Shape(x, y), r(r) {}
	double area() const override { return PI * r*r; }
};

// achieve polymorphism by passing a reference of a Shape Object
void demo1(Shape const& s) {
	std::cout << "Area of Shape is " << s.area() << endl;
}

// achieve polymorphism by passing a pointer of  a Shape Object which points to a derived class such as Circle
void demo2(Shape * const s) {
	std::cout << "Area of Shape is " << s->area() << endl;
}

// since Shape is abstract, we cannot instantiate it,
// this is not allowed, however we can use reference and pointer from Shape 
//
//
//void demo3(Shape  const s) {
//	std::cout << "Area of Shape is " << s.area() << endl;
//}


int main()
{
	Circle c1(1, 1, 2);

	// achieve polymorphism by passing a pointer of  a Shape Object which points to a derived class such as Circle
	Shape * pc = &c1;

	pc->getlocation();

	demo2(pc);  // achieve polymorphism by passing a pointer of  a Shape Object which points to a derived class such as Circle
	demo1(c1);  // achieve polymorphism by passing a reference of a Shape Object

	//demo3(c1);  // not allowed here because Shape is abstract now


}
Shape itself may be abstract, but Circle isn't abstract, and Circle is a Shape. Therefore, you can create a Circle object, even though you can't create an object that is only of type Shape.
Last edited on
masterinex wrote:
does that mean that even though c1 was not dynamically created, the call demo(c1); is dynamic

those are different meanings of the word "dynamic". "Dynamic" as in allocated on the heap is not related to "dynamic" as used in dynamic polymorphism. I never really encounter "dynamic" used as "allocated on the heap" except in bad tutorials. I think it acquired that meaning from "dynamic arrays", arrays whose size is not known until run time (dynamic!) had to be allocated on the heap in C before 1999.

"Static" is even worse: it could mean "member of a class and not its instance", or "persisting as long as the program runs", or "not visible to the linker", or "known at compile time".

The two major kinds of polymorphism: static polymorphism and dynamic polymorphism, use these words to mean "known at compile time" and "not known until run time"

masterinex wrote:
it chooses the correct function Circle::Area, because you are passing c1 as a reference of Shape.

it chooses the correct function because the function is marked virtual (which delays the decision on what to call until run time) and because the dynamic ("not known until run time") type of s is actually Circle, not Shape.

masterinex wrote:
why is that I always find that objects are created dynamically with the "new" keyword in conjunction with Polymorphism

I blame Java.
Last edited on
Cubbi wrote:
I never really encounter "dynamic" used as "allocated on the heap" except in bad tutorials. I think it acquired that meaning from "dynamic arrays", arrays whose size is not known until run time (dynamic!) had to be allocated on the heap in C before 1999.

Sorry, a little off topic, but is "dynamic array" correct terminology in itself when referring to arrays sized at run-time? (Which in standard C++ has to be done using new/malloc or a wrapper around new).
Last edited on
is "dynamic array" correct terminology in itself when referring to arrays sized at run-time

Scanned the standard for this one.. it defines four terms that all use the adjective "dynamic" in them: "dynamic type" (related to polymorphism), "dynamic initialization" (not related to polymorphism or to the heap in any way), "dynamic storage duration" (basically, the heap), and "dynamic cast" (related to polymorphism). It also uses the general OOP term "dynamic binding" once and the colloqualism "dynamically allocated memory" several times instead of "dynamic storage".

It does say "Objects can be created dynamically during program execution, using new-expressions" (http://eel.is/c++draft/basic.stc.dynamic), so an array so created would be a dynamic array (or, to be technical, an array with dynamic storage duration)

maybe I'm being too harsh on "dynamic objects".. it's just that the word has too many meanings.
Last edited on
1
2
Shape itself may be abstract, but Circle isn't abstract, and Circle is a Shape. Therefore, you can create a Circle object, even though you can't create an object that is only of type Shape.



but if u call the constructor Shape when u create a new object of type Circle , aren't u creating an instance of Shape aswell through the constructor of Shape ?
> lets say i create these objects statically:
> Circle c2(2, 1, 1);
> and then push them as a shared pointer in a vector ?
> v.push_back(make_shared <Circle>(c2));
> does that make the objects become dynamic because I have used make_shared ?
not sure if I'm reading this correctly.
The nature of `c2' would not change, it will be continue to being a `Circle' allocated on the stack.
The vector will have a copy, another object, that's referenced through a smart pointer.

> how is it possible for us to call the constructor of Shape ,even though now Shape is abstract ?
you need to review some theory, try to answer these questions:
¿what does it mean that a class is abstract?
¿why would I want a class to be abstract?
¿what is the state of an object?
Last edited on
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

from http://en.cppreference.com/w/cpp/memory/shared_ptr/make_shared

can someone give me an example of how this template is being called ? what values should i choose for Args&&...args and also how does it uses the "new " keyword ?
The arguments that you pass to make_shared or make_unique are the arguments that you want to pass to the constructor. If you want to use the default constructor you simply pass no arguments at all.
can someone give me an example of how this template is being called


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct A { 
  A() {} // default constructor
  A(int, int) {} // constructor accepting 2 ints;
  A(A const&) {} // copy constructor;
};

auto p0 = std::make_shared<A>();
// roughly equivalent to
// auto p0 = std::shared_ptr<A>(new A());

auto p1 = std::make_shared<A>(1, 2);
// roughly equivalent to
// auto p1 = std::shared_ptr<A>(new A(1, 2));

auto p2 = std::make_shared<A>(*p0);
// roughly equivalent to
// auto p2 = std::shared_ptr<A>(new A(*p0)); 
so in auto p1 = std::shared_ptr<A>(new A(1, 2));

1,2 are args ?
Yes, coarsely.

make_shared<T>(arguments to the constructor of the new T)

Says:
"Allocate an object of type T, then initialize it with the stuff in parentheses. Make a shared_ptr referring to that object."

The only template argument you should specify is T, the first one.

Remember that function templates tell the compiler how to write functions. When you write
std::make_shared<Circle>(1, 2);

The compiler writes a function for you named make_shared<Circle> that looks (mostly) like this:
1
2
3
shared_ptr<Circle> make_shared<Circle> (int a, int b) {
   return std::shared_ptr<Circle>(new Circle(a, b));
}

And then goes ahead and calls it with 1 and 2 as the arguments.
Last edited on
Topic archived. No new replies allowed.