Benutzer:Dirk Huenniger/ Java2cpp
This page is intended to be a very concise tutorial for Java developers who want to learn C++. It might also be useful for people who are familiar with an other object oriented language an want to learn C++.
Hello World
[Bearbeiten]First of all lets look at the hello world program in C++ as well as in Java.
Hello World in Java
[Bearbeiten]public class Hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Hello World in C++, the old way
[Bearbeiten]One way of doing it in C++ is the C way of doing things. It is considered deprecated, and there are good reasons not to use it anymore. But it still is a possible way of doing it and I like it, so I show it
#include <cstdio>
int main(){
printf("%s", "Hello World\n");
return 0;
};
Let's take a look at the differences to the above Java code. In Java the main method is a static method of the object Hello. In C++ the main method is not a part of an object. This is why it is often called main function. In Java the main method return void. In C++ it returns an exit code. The exit code is returned to the program that has called the "Hello World" program, it is often used to signal whether the called program worked as expected. A value of 0 stands for "everything worked fine". Furthermore we see that the printf method is a function too (so it does not belong to an object), the "%s" means that we want to print a string, and the "\n" means that we want to start a new line after we printed the string. In order to use the printf method we have to incluse a head file #include <cstdio> , its very close to importing a package in Java.
Hello World in C++, the new way
[Bearbeiten]The new way of doing it is using a syntax that only works in C++.
#include <iostream>
using namespace std;
int main(){
cout << "Hello World" << endl;
return 0;
};
This time we have to include an other header file #include <iostream> . Furthermore we imported the namespace of the package using namespace std;. If we hadn't done it we would have had to write std::endl and std::cout. cout means that we want to write to standard output and endl means that we want to start a new line after we wrote the string. It is very interesting to understand how c++ processes the line and especially what it does with the << symbol. First of all lets add brackets to show which << symbol is processed first:
((cout << "Hello World") << endl);
The inner expression returns cout. So the outer expression reads;
(cout << endl);
But before returning cout the inner expression writes the string "Hello World" to the standard output. Of course the outer expression also returns cout but this result is not used for anything so it just fades away.
Pointers Values References
[Bearbeiten]The way Java does it
[Bearbeiten]It is interesting to see how parameters are passed to a method. First we take a look at an example in Java.
class Mutable{
public int k;
}
public class Paramter {
public static void a(Mutable m){
m.k=3;
}
public static void b(int j){
j=3;
}
public static void c(Mutable t){
Mutable p=new Mutable();
p.k=3;
t=p;
}
public static void main(String[] args) {
Mutable n=new Mutable();
n.k=0;
a(n);
System.out.println(n.k);
int y =0;
b(y);
System.out.println(y);
Mutable u=new Mutable();
u.k=0;
c(u);
System.out.println(u.k);
}
}
The output is
3
0
0
Obviously there is a difference between passing a primitive type such as int, or an object. If we pass an it to a function it is copied and the copy is passed to the called method. If we pass an object to a method, a new pointer to the object is created and this pointer is passed to the called method. That is the command m.k=3
dereferences a pointer that is pointing to the same object as the pointer n
and thus modifies the integer referred to as n.k
. As we look at the method c we see that we get zero again.
In Java we often say u is a Mutable and the same way we say y is an int. This is ok for everyday live. But as we saw in the above examples there is a difference in the meaning of to be in between being an object and being a primitive type. For learning C++ it is helpful to say that y is and integer but u is a pointer pointing an object. So u is not an object, and y is not a pointer.
The way C++ does it
[Bearbeiten]Now we will rewrite the same program in C++. Here we have to be very precise in differentiating between objects and pointers to objects. Look at the result before looking at the code.
#include <iostream>
using namespace std;
class Mutable{
public:
int k;
};
class Parameter {
public:
static void a(Mutable * m){
m->k=3;
}
static void b(int j){
j=3;
}
static void c(Mutable * t){
Mutable* p=new Mutable();
p->k=3;
t=p;
delete p;
}
static void main(){
Mutable* n=new Mutable();
n->k=0;
a(n);
cout <<n->k << endl;
int y =0;
b(y);
cout<<y<<endl;
Mutable* u=new Mutable();
u->k=0;
c(u);
cout <<u->k<<endl;
delete n;
delete u;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
the output is
3
0
0
so it is just the same as in the above case. The main function is a bit magical, but not important for now, it just calls the main method of the class Parameter. You see that we write Mutable*
instead of Mutable
and u->k
instead of u.k
. This is because we need to say that we are talking about pointers to objects not objects itself. Furthermore there are two calls to delete
this is because C++ does not have a garbage collector and we have to say which objects we don't need anymore. Ok you can have something like a garbage collector in C++ if you use reference counted pointers, like the ones you find in the boost library, but this is not our concern here. In this example
we have dealt with y just the way Java does. So y is an integer but not a pointer to a pointer to an integer. If you want y do be pointer you can
also write a source code to accomplish this. If you also write the method b in a suitable way you can change the value of the integer pointer to by y in the method b. Just for completeness we show the source.
#include <iostream>
using namespace std;
class Parameter {
public:
static void b(int* j){
*j=3;
}
static void main(){
int *y =new int;
*y=0;
b(y);
cout<<*y<<endl;
delete y;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
the output is
3
Maybe we should talk a little about the meaning of *. The asterisk has got two meanings: If you declare a variable using it like in int *y
it means that y is not an integer but a pointer pointing to an integer. And other use is when using y. If you got y but want to talk about the integer pointer to by y you have to write *y
. So cout<<*y<<endl;
prints the integer pointed to by y and *y=0
sets the integer pointed to by y to zero.
Objects direct (only in C++)
[Bearbeiten]In C++ you can talk to objects directly, that is without pointers, something you can not do in Java. You can deal with objects just the way Java deals with primitive types.
#include <iostream>
using namespace std;
class Mutable{
public:
int k;
};
class Parameter {
public:
static void a(Mutable m){
m.k=3;
}
static void b(int j){
j=3;
}
static void c(Mutable t){
Mutable p=Mutable();
p.k=3;
t=p;
}
static void main(){
Mutable n=Mutable();
n.k=0;
a(n);
cout <<n.k << endl;
int y =0;
b(y);
cout<<y<<endl;
Mutable u=Mutable();
u.k=0;
c(u);
cout <<u.k<<endl;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
The output is
0
0
0
That is you got the object n and if you pass it to the method a you create a copy of it which is called m inside the method a. This way the method m does not get its hands on n. And thus n.k remains unchanged. Very similar things happen in the other methods. It is important to note that n is no longer a reference to an object it is the object itself, just like a primitive type in Java.
Objects by Reference (only in C++)
[Bearbeiten]In C++ there is one more funny thing you can do when passing objects. It is called pass by reference. A reference is something like a pointer it is even stronger. You say that an object is passed by reference by using the ampersand when you write the parameter list of the method. We will just look at an example to see how it works.
using namespace std;
class Mutable{
public:
int k;
};
class Parameter {
public:
static void a(Mutable &m){
m.k=3;
}
static void b(int &j){
j=3;
}
static void c(Mutable &t){
Mutable p=Mutable();
p.k=3;
t=p;
}
static void main(){
Mutable n=Mutable();
n.k=0;
a(n);
cout <<n.k << endl;
int y =0;
b(y);
cout<<y<<endl;
Mutable u=Mutable();
u.k=0;
c(u);
cout <<u.k<<endl;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
The output is
3
3
3
So in the case of the method a it just works like the pointer version worked, which is also the way things work in Java.
In the second case (method b) it is also like the pointer version, but it is not the same as in the Java version, since primitive types (like int) are used directly (without pointer) in Java. But method c is really interesting. None of the previous codes produced a 3 in this case. A reference to the object u is passed to the method c. This reference is called t. And in the line t=p
the content of p is copied into the object referred to by t, overwriting any previous content. Since t refers to u. The content of p is written into u overwriting any previous content of u. So a reference is something similar to a pointer but not exactly the same. For completeness we also show an implementation c that uses pointers and produces the same result.
#include <iostream>
using namespace std;
class Mutable{
public:
int k;
};
class Parameter {
public:
static void c(Mutable *t){
Mutable p=Mutable();
p.k=3;
*t=p;
}
static void main(){
Mutable* u=new Mutable();
u->k=0;
c(u);
cout <<u->k<<endl;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
The output is
3
So you can reach the same by using pointers. The major trick is the line *t=p
.
Reference Counting (C++ Garbage Collection)
[Bearbeiten]In the example about Pointers we have seen that you need to call delete exactly once for every object that you have allocated with new. That is a bit cumbersome, and you often forget to call delete and thus create a memory leak, or you call delete but try to operate on the deleted object, which causes your program to crash. In Java you don't have the problem since there is a garbage collector that deletes the object exactly when you don't need it anymore. There are different way of implementing a garbage collector and there even is one in C++. It is not build into the language but comes with the Boost library. It is called shared pointer, or reference counted pointer. The way this implementation works is Ok. It is very fast but also very stupid. If you have got two objects a and b and a has got a shared pointer to b and b has got a shared pointer to a none of the objects will be deleted even if nothing else has got a shared pointer to a or b anymore. So there is a way to create a memory leak even if you use shared pointers everywhere. So you still have to be very careful not to create cyclic references. Well if you use shared pointers everywhere you will at least not cause a crash of your program by operating on a deleted object anymore. And to be true cyclic references are not needed very often, still they often appear by clumsiness of the programmer. In case you really need to create a cyclic reference and want it to be processed correctly you need to use weak pointers to break the cycle, but here they are a bit off topic. So here comes the shared pointer version of the above program.
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class Mutable{
public:
int k;
};
class Parameter {
public:
static void a(shared_ptr<Mutable> m){
m->k=3;
}
static void b(int j){
j=3;
}
static void c(shared_ptr<Mutable> t){
shared_ptr<Mutable> p(new Mutable());
p->k=3;
t=p;
}
static void main(){
shared_ptr<Mutable> n(new Mutable());
n->k=0;
a(n);
cout <<n->k << endl;
int y =0;
b(y);
cout<<y<<endl;
shared_ptr<Mutable> u(new Mutable());
u->k=0;
c(u);
cout <<u->k<<endl;
}
};
int main(){
Parameter e;
e.main();
return 0;
}
The output is
3
0
0
So we see that everything works just like in the pointer example, with is exactly the way things work in Java, but there are no calls to delete anymore thus the garbage collector is talking care of proper clean up. We have seen many ways of accessing object in C++ now and each of them might be useful in a particular situation and it is nice that C++ allows so many ways of doing it. But this way it also passes a lot of responsibility to the programmer. In Java you don't have so many choices and thus are forced to do it the way Java prescribes, which is very close the shared pointer mechanism in C++. So as a rule of thumb I recommend to use the shared pointer mechanism for everything you do in C++. Still this is just a rule of thumb and if you think one of the other ways is more suitable in your particular situation you should not hesitate to use it.
Iterators and Sequences
[Bearbeiten]A Collection in Java
[Bearbeiten]First of all lets look how you create a sequence in Java and how you can iterate over it.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Collections {
public static void main(String[] args) {
List<Integer> l=new ArrayList<Integer>();
for (int i=0;i<3;i++) {
Integer ii=new Integer(i);
l.add(ii);
}
for (Integer j : l) {
System.out.println(j);
}
Iterator<Integer> it=l.iterator();
while (it.hasNext()) {
Integer j=it.next();
System.out.println(j);
}
}
}
The output is:
0
1
2
0
1
2
Thus we have seen two ways to iterate over a sequence. The first way is much more cute, unfortunately only the second way is possible in C++.
A Container in C++
[Bearbeiten]Lets see how the same program looks in C++:
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <vector>
using namespace std;
using namespace boost;
int main() {
shared_ptr<vector<shared_ptr<int> > > l (new vector<shared_ptr<int> > );
for (int i=0;i<3;i++) {
shared_ptr<int> ii=shared_ptr<int>(new int);
*ii=i;
l->push_back(ii);
};
vector<shared_ptr<int> >::iterator it;
it=l->begin();
while (it!=l->end()) {
cout << **it<< endl;
it++;
}
}
The output is
0
1
2
Ok I agree this looks quite ugly. But therefor it is really close to the Java. The Integer as well as the ArrayList<Integer>
are objects. I wanted all objects to be reference counted just like in Java. This way we have shared_ptr<int> ii
instead of just int
. When it comes to the list we have to uses shard_ptr twice once for the list and once for the integers in the list. Thus we got shared_ptr<vector<shared_ptr<int> > > l
. By the way you can not write shared_ptr<vector<shared_ptr<int>>> l
but this is likely to change when the next standard of C++ is defined. The problem occurs because << is a special operator in C++. A C++ programmer would usually have written a different code for this problem. It is likely that he would have used the objects direct idiom. Thus he would have avoided any calls to new and any use of shared_ptr. But as we have seen earlier the behavior would differ from what Java does especially when calling functions. So I am a bit reluctant to show you the other implementation. The code in this implementation looks very simple and looks very similar to the way code looks in Java, but it behaves totally different, in certain situations. So the danger is that you just write code this way and expect it to run like it ran in Java and get very upset if you find out that it doesn't. You might even think that C++ is a bad language just because it does not work exactly the same
way as Java works. With this disclaimer out of the way, here is the other implementation:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> l;
for (int i=0;i<3;i++) {
l.push_back(i);
};
vector<int>::iterator it;
it=l.begin();
while (it!=l.end()) {
cout << *it<< endl;
it++;
}
}
The output is
0
1
2
So you see the code looks much more simple. Still I consider this way of doing things to be dangerous especially for Java programmers since you talk to object directly, which is very different from having pointers to objects, which you got in Java. So I really think it is worth the extra typing effort and to do everything with a shared pointer. But I am still showing you the other way of doing it, and I leave the choice to you. But I will still show you once more what the problem is you have to be careful about:
#include <vector>
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class Mutable{
public:
int k;
};
void a() {
vector<Mutable> l;
for (int i=0;i<3;i++) {
Mutable m;
m.k=i;
l.push_back(m);
};
Mutable z=l[1];
z.k=42;
vector<Mutable>::iterator it;
it=l.begin();
while (it!=l.end()) {
cout << it->k<< endl;
it++;
}
}
void b() {
shared_ptr<vector<shared_ptr<Mutable> > > l(new vector<shared_ptr<Mutable> >);
for (int i=0;i<3;i++) {
shared_ptr<Mutable> m(new Mutable());
m->k=i;
l->push_back(m);
};
shared_ptr<Mutable> z((*l)[1]);
z->k=42;
vector<shared_ptr<Mutable> >::iterator it;
it=l->begin();
while (it!=l->end()) {
cout << (*it)->k << endl;
it++;
}
}
int main() {
a();
b();
}
The output is
0
1
2
0
42
2
The second code does it the same way Java does it. But the first one obviously does not. So you have to be aware that life is different in C++ if you don't use shared pointers everywhere. Just for completeness I will also show you the equivalent Java code.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
class Mutable{
public int k;
}
public class CollectionsMixed {
public static void main(String[] args) {
List<Mutable> l=new ArrayList<Mutable>();
for (int i=0;i<3;i++){
Mutable m=new Mutable();
m.k=i;
l.add(m);
}
Mutable z=l.get(1);
z.k=42;
for (Mutable m:l ){
System.out.println(m.k);
}
}
}
the output is
0
42
2
So this is why you have to be careful.
Sequence Containers
[Bearbeiten]The following tables shows the sequence containers in C++ and their siblings in Java.
Java | C++ |
---|---|
ArrayList | vector |
LinkedList | list |
- | deque |
deque is something between list and vector. A list is good at insertion but bad a retrieval by index. A vector is good as retrieval by index but bad at insertion. The deque is good at insertion at its end or at it beginning, but not in between, and its still reasonably good at retrieval by index. This is why deque is actually an acronym for double ended queue.
Maps and Sets
[Bearbeiten]The following tables shows the associative containers in C++ and their siblings in Java. I also included some container of the Boost library for C++. If you are looking for a particular container that is not part of the C++ standard library you can often find it in the "Unordered" package of the Boost library.
Java | C++ | Boost |
---|---|---|
TreeMap | map | - |
HashMap | - | unordered_map |
TreeSet | set | - |
HashSet | - | unordered_set |
In C++ each of this container has a "multi" brother. So set has got multiset and so on. In these containers you can store more than one item per key.
Lets just look at an example for the usage of set. In Java it looks like this:
import java.util.TreeSet;
public class SetTest {
public static void main(String[] args) {
TreeSet<Integer> s=new TreeSet<Integer>();
s.add(1);
s.add(2);
s.add(3);
s.add(1);
for (Integer i : s) {
System.out.println(i);
}
}
}
the output is
1
2
3
So far everything looks nice. No lets look at the same thing in C++:
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <set>
using namespace std;
using namespace boost;
template<class T>
class CmpPointer : public shared_ptr<T> {
public:
CmpPointer<T> (T* i):shared_ptr<T>(i){
}
friend int operator<(const CmpPointer<T>& left,const CmpPointer<T>& right) {
return *left < *right;
}
};
int main() {
CmpPointer<int> one( new int);
*one=1;
CmpPointer<int> two( new int);
*two=2;
CmpPointer<int> three( new int);
*three=3;
CmpPointer<int> x( new int);
*x=1;
shared_ptr<set<CmpPointer<int> > > s(new set<CmpPointer<int> >);
s->insert(one);
s->insert(two);
s->insert(three);
s->insert(x);
shared_ptr<set<CmpPointer<int> >::iterator> it(new set<CmpPointer<int> >::iterator);
*it=s->begin();
while (*it!=s->end()) {
cout << ***it<< endl;
++*it;
}
}
the output is
1
2
3
Looking at the code a lot of questions will arise. First of all: "Was there something wrong with the stuff you smoked?". Well Ok I agree that ***it
looks slightly unfamiliar. The interesting point is CmpPointer. It is essentially the same as shared_ptr, just that it has inherited the less than comparison from the type pointer to by CmpPointer, in this case int. This is needed since the treemap needs a less than comparison in order to order the objects in the underlying tree. Ok the Java version looks much shorter. But just for your amusement I show the same program in Python:
for x in set([1,2,3,1]):print (x)
A fried on mine also contributed an example an Perl.
map{$h{$_}++||print}(1,2,3,1)
the output is
1
2
3
It is obviously even shorter. Still the Python code looks most charming to me.
Destructor (finalize in C++)
[Bearbeiten]C++ is one of the very few languages that directly support destructive programming. Well many programmers seem to be able to do it even in languages that don't directly support it. Ok I am just kidding. In C++ there is something that a special method that is called when an object ceases to exists. It is roughly equivalent to the finalize method in Java. Still in C++ it is quite clear when this method is called and it is also guaranteed that it is called. There is an exception to this rule if you use reference counted pointers and create a cyclic reference. Usually the Destructor is called when an object goes out of scope. In case of reference counted pointers it is called when the last reference counted pointer to the object goes out of scope. Going out of scope roughly means getting unaccessible in the program. So if you method return from a method all local variables of this method go out of scope, but the local variables of the calling method stay in scope. Let us look at an example to illustrate this point a bit.
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class A {
public:
int x;
A(int y) {
x=y;
cout << x <<" born"<<endl;
}
virtual void say(){
cout << x <<" says hi"<<endl;
}
virtual ~A() {
cout << x <<" died"<<endl;
}
};
void u(shared_ptr<A> number) {
shared_ptr<A> two( new A(2));
two->say();
number->say();
}
int main() {
shared_ptr<A> one( new A(1));
u(one);
{
shared_ptr<A> three( new A(3));
three->say();
}
cout << "end"<<endl;
}
The output is:
1 born
2 born
2 says hi
1 says hi
2 died
3 born
3 says hi
3 died
end
1 died
In this code we see a constructor just as in Java, but we also see a destructor virtual ~A()
. It is a special method that is called when an object goes out of scope. You can just follow the course of the execution and check against the output. It is interesting to see that 2 is in created in function u and bound to a the local shared_ptr two in u. When the function is left the scope of the function ends, thus the shared_ptr to two goes out of scope and since it is the only shared_ptr pointing to 2, 2 also gets destroyed. The shared_ptr number also goes out of scope at that time and gets destroyed, but since there is still the shared_ptr one pointing to 1, 1 does not get destroyed at that time. Then the shared_ptr three is bound to the newly created object 3. The closing curly bracket also signals an end of a scope. Thus the shared_ptr three goes out of scope and since there is not pointer pointing to 3 anymore it gets destroyed too. Finally the last shared_ptr pointing to 1 goes out of scope and 1 gets destroyed.
Serialization
[Bearbeiten]Serialization is the process of converting an object to sequence of byte. This is usually done in order to store an object to a file, or to send an object across a network. C++ itself does not provide any means of serialization. In Boost there is a nice implementation it is slightly more difficult to use then the Java mechanism, but it is still quite easy.
Overloaded operators (a special feature of C++)
[Bearbeiten]In C++ you can overload operator, that is you can assign a new meaning to operator like +,-,*,/ and many others. For some mathematical applications this is quite charming. For example you can define a class for complex numbers and say a*b where a and b are complex numbers and a*b evaluates to their complex product. Or you can define a class for vectors and define a+b to mean the vector sum of the vector a and b. On the other hand it is considered bad style by some people because it is not always obvious what a+b means and people might get very confused if you use this feature extensively. In Java you don't have anything like that, and thus you don't have the choice to use it. In C++ you can use it but you are also responsible for choosing whether to use it or not, which is not always simple.
Pointers to Functions (callbacks in C++)
[Bearbeiten]In Java you usually use implement an interface like Callable if you want to write a callback function you often use an anonymous inner class for that. In C++ you do the same thing in a different way.
Dynamic Loading
[Bearbeiten]In Java you come across dynamic loading virtually everywhere, in Java classes are dynamically loaded as needed. Usually you don't see it since the JVM does it in background without you taking any note of it. But you can also ask for this explicitly. In C++ nothing like that happens automatically. All classes to be used by a C++ program are compiled and linked into a single executable and only this executable is loaded. And nothing is loaded after the program has started. But there is a way to overcome this limitation and to load parts of your program while it is already running. This is called dynamic loading. We will see how it works in C++ and Java
Templates vs. Generics
[Bearbeiten]In Java you know about generics. A syntactically very similar mechanism exists in C++.
Virtual Methods
[Bearbeiten]The virtual keyword is about overloading functions. As a rule of thumb, always write virtual when you declare a function. It is needed to get proper overloading behavior, often called polymorphism. First of all lets see how overloading works in Java.
class A {
public void say() {
System.out.println("A");
}
}
class B extends A {
public void say() {
System.out.println("B");
}
}
public class Virtual {
public static void main(String[] args) {
B b=new B();
A a=b;
a.say();
}
}
The output is
B
a is a pointer that points to an object that is of type A as well as of type B. Since A is the basic type and B is the extended type, Java chooses to call the method say of type B when asked to call the say method. This is very useful when you first write code for the basetype A in which you call the say method and later on you write the class B in which customize the behavior of say without touching the original code. And this is usually what you want to happen. In C++ you have to use the virtual keyword in order to accomplish this. If you don't you get a result that you don't expect, as we will see now:
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class A {
public:
void say() {
cout << "A" << endl;
}
};
class B : public A {
public:
void say() {
cout <<"B"<< endl;
}
};
int main() {
shared_ptr<B> b(new B());
shared_ptr<A> a;
a=b;
a->say();
}
the output is
A
which is not what we want. In this case a is a pointer to an object of type A. And for that reason C++ decides to call the say method of type A. Now lets see what happens when we just add the virtual keyword.
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class A {
public:
virtual void say() {
cout << "A" << endl;
}
};
class B : public A {
public:
virtual void say() {
cout <<"B"<< endl;
}
};
int main() {
shared_ptr<B> b(new B());
shared_ptr<A> a;
a=b;
a->say();
}
the output is
B
Now everything works the way we want it to work. If a method is declared virtual C++ will look for the derived class that implements it and call its implementation, even if we call it for a pointer that is typed to point to an object baseclass, but acutally points to an object of a derived class. Now we will look at what happens if we don't use pointer but rather use objects directly.
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
using namespace boost;
class A {
public:
virtual void say() {
cout << "A" << endl;
}
};
class B : public A {
public:
virtual void say() {
cout <<"B"<< endl;
}
};
int main() {
B b=B();
A a=b;
a.say();
}
the output is
A
Here the mechanism also does not work. The reason is that the line a=b
does something really strange. Since a and b are Objects and not pointers to object the assignment is understood as a request to copy any content of b into a. a stays an object of type A and since it does not have any attributes nothing is copied. So a call to say causes the say method of type a to be called. If A has got some attributes and B has got some more only the attributes that are declared in A are copied from b into a. All attributes defined in B are simply not processed. This effect is called truncation and usually undesired. So I recommend. Use a shared_ptr for every object and always use virtual if you declare a method. There is one good reason not to use virtual. If you are sure you will not need to overload it anyway you can chose not to declare it virtual, in this case this calls to all methods of this object will be faster, since C++ does not need to search for the right method to call anymore, but you won't be able to overload the function in the way we just showed. You can still define a different implementation for the method say for type B and you can still call it if you have a pointer that is typed to point to and object of type B, even if you don't use virtual. But if you don't use virtual it does not work for the baseclass anymore and that is usually a serious problem.