However, in C++, I found out that an object has to be a pointer for me to properly implement polymorphism.
Sort of.
Pointers, references, or some other advanced programming techniques under the bucket of "type erasure" (that under the hood use pointers or references somewhere) can all provide dynamic polymorphism, like Java.
Value objects - which Java doesn't support for user-written classes - cannot be _dynamically_ polymorphic, but can be _statically_ polymorphic (a concept that mostly doesn't exist in Java). For instance, C++ functions and classes can be generated with templates, so you can write a single function that takes any type of value and operates on it.
Functions that operate on objects polymorphically can work on any kind of object in C++. e.g. you can get a reference or pointer to any object, no matter how it was created. The function doesn't and shouldn't care where the object came from.
That said, dynamic polymorphism is a bit rarer in C++ than Java, typically because polymorphism and class inheritance and such are _bad solutions_ for a number of problems. Java programmers tend not to notice how bad those solutions are because Java _forces_ people to use polymorphic objects for everything; C++ gives you a lot more options for when dynamic polymorphism is a suboptimal tool for the job.
Some examples:
// references should be preferred over pointers in most cases
void foo(base_type& ref) {
ref.method(); // dynamically polymorphic
}
// higher level abstractions can also be polymorphic and are sometimes very preferable over base classes and inheritance
void foo(function<void(int)> func) {
func(42); // "polymorphic" because func can be bound to a free function, member function, lambda, etc.
}
// templates are a form of static (compile-time) polymorphism
template <typename T>
void foo(T val) {
val.method(); // tries to invoke T::method() on val, no matter what T is
}
In general, remember two things:
- you should _very rarely_ ever have a raw pointer in C++. If you're passing around ownership of an object, use std::unique_ptr or std::shared_ptr, and if you're just observing an object, use a reference or std::weak_ptr (and __strongly__ prefer unique_ptr and references as shared_ptr/weak_ptr can cause more problems than they solve if you're not careful!).
- don't use inheritance to solve every problem. that's Java-esque thinking that should be stamped out when moving to another language. :) inheritance solves a small few particular types of problem very well but leads to either inflexible code, overly-coupled code, or poorly-performining code when it's overused.