In Java, a variable of a class type is always a reference to the object. I.e.
A a = new A();
A a2 = a; // now a and a2 represent the same object
a.x = 12; // now print(a2.x) will also output 12
new A() will create the actual object on the heap (area of memory that can be dynamically allocated), and return the memory address of it. a and a2 will (internally) actually contain only that address.
Unlike integral type variables, which contain the actual value, so:
int a = 1;
int a2 = a;
a = 3;
// now a2 will still be 1
In C++ objects are put on the stack, the same way as integral types (unless you use references or pointers). So:
class A {
public:
int x;
};
A a;
A a2;
a.x = 1;
a2 = a; // actually calls a2.operator=(a) which is implicitly defined, and assigns all data members (here only int x) the values from the other A
a.x = 2;
std::cout << a2.x << std::end; // will output 1
This is still true for arrays: If you declare an array A as[10], as is a series of 10 A objects allocated on the stack.
Inheritance
If you have
class A {
public:
int x;
};
class B : public A {
public:
int y;
};
A a;
a.x = 1;
B b;
b.x = 2;
b.y = 500;
// and then:
a = b; // now a.x == 2, but a holds no information about the y value
// with arrays it is the same:
A as[2];
as[0] = b; // now as[0].x == 2, --
Doing a = b copies only the values from b from its superclass, into a.
References
A a;
a.x = 1;
A& a_ref = a;
now a_ref is a reference to a
unlike in Java the reference can't be made to point to another object.
Doing a_ref = a2 would instead have the same effect as a = a2
a.x = 2
std::cout << a_ref.x << std::endl; // now outputs 2
Also unlike Java, a has to exist for as long as a_ref exist, otherwise a_ref is invalid and accessing it will make the program crash. In Java objects are allocated on the heap, and deallocated only when no reference exists that points to it anymore (this is called garbage collection).
B b;
A& a_ref2 = b;
// now a_ref2 is a reference to b.
// (aka B has been downcasted)
// to access b.y from a_ref2, you could do:
int the_y = static_cast<B&>(a_ref2).y
This (static upcasting), is not recommended and only works when you are sure that a_ref2 points to a B object. Otherwise it fill segfault/crash.
If A is polymorphic (see below), dynamic_cast<B&>(a_ref2) can be used instead (still not recommended). It detects the error if a_ref2 is not a B and throws an exception.
Polymorphism
class A {
public:
virtual int get_v() { return 1; }
int get() { return 1; }
int get_a() { return 3; }
};
class B : public A {
public:
int get_v() override { return 2; } // 'override' is not needed, and only works in C++11
int get() { return 2; }
};
B b;
A& a_ref = b;
b.get_v(); // returns 2
b.get(); // returns 2
b.get_a(); // returns 3, by inheritance
a_ref.get_v(); // returns 2, because get_v is a virtual function.
Because of the virtual function A and B are polymorphic classes. It is not known at compile time whether this will call A::get_v or B::get_v, since a_ref is of type A&. Instead the function to call is decided at runtime, depending on what a_ref points to. In Java all functions are like this.
a_ref.get(); // returns 1.
Because get() is not polymorphic, it calls A's get() function since a_ref is of type A&.
a_ref.get_a(); // returns 3, by inheritance
Pointers
Pointers are like references, but lower level. You access references the same way as actual objects (a_ref.x and b.x). The pointer variable is the address. Unlike references, they can be made to point to another object after initial assignment:
B b; // same classes A and B as before
A* a_ptr = &b; // a is now a pointer to b.
// The type A* means "pointer to B". &b means "address of b".
// Polymorphism works the same:
a_ptr->get_v(); // returns 2
a_ptr->get(); // returns 1.
a_ptr->get_a(); // returns 3.
Dynamic memory allocation also returns pointers:
A* a_ptr = new B(); // a_ptr is now a pointer to a B allocated on the heap
...
delete a_ptr; // must be done to deallocate the B, otherwise there will be a memory leak.
Because a_ptr is of type A* (and not B*), the destructor of a polymorphic class should be virtual:
class A {
public:
...
virtual ~A() { .... }
};
class B : public A {
public:
...
~B() override { .... }
};
otherwise only A::~A would get called.
So in your case, you could do:
A* array[10];
array[0] = new B();
array[0]->get_v();
delete array[0];
But all the pointers in the array that haven't been initialized would be invalid. I.e. array[1]->get_v() or delete array[1] would be an error.
array would then be an array of pointers to objects of type A, or a subclass of A.
You can also dynamically allocate an array of A's like this:
A* array = new[10] A; // array is now a pointer to an array
array[0].x = 1;
delete[] array; // Deallocates as many items as where allocated
But this is the same as doing A as[10] as before, just on the stack. These would be A objects , and doing array[0] = b would just copy the b.x from b.
unique_ptr
A good solution would probably to use std::unique_ptr from the STL. It is a wrapper for a pointer, such that no two unique_ptr can point to the same item. (because copying the unique_ptr is forbidden, etc.):
#include <memory>
// needed for std::unique_ptr
std::unique_ptr<A> array[10];
array[0].reset( new B() ); // needed to assign a value to it
array[0]->get_v();
works as expected. The elements of array that have not been assigned a value are default-initialized to zero. Accessing them would throw an exception instead of a segfault etc.
Items can't be assigned with = (because the semantics are different). Instead, reset assigns the pointer. If it had another pointer before, that one is delete'd first. Also no delete array[0] are needed (or possible), the unique_ptr deletes the items when the variable array goes out of scope.
A solution to allow multiple pointers pointing to the same object, such that the object gets deallocated only when no pointer points to it anymore, is also in the STL: shared_ptr.
Array constructors
For an array of items on the stack like
A array[10];
It will always call the default constructor (with no arguments). There is no way to pass arguments to it.
std::array can be copy-initialized (i.e. constructor taking single value:)
class A {
public:
int x;
A(int nx) : x(nx) {}
};
std::array<A, 3> ar = { 1, 2, 3 };
// or
std::array<A, 2> ar2 = { a, a2 }; // makes copies
A* array[10];and then writearray[0] = new B();. You can cast to base class only references and pointers of derived class. Simply doingarray[0] = new B();not working because it is impossible to assignA*toA.vector<unique_ptr<A>>