C++ for Java developers

(You should be an experienced Java programmer to follow this guide. It will explain the pitfalls that can be encountered when starting C++.)

Declaration is definition

The class std::vector has several constructors. One of them is defined as: std::vector::vector(std::size_t count). This constructor will create a default-initialized vector of count elements. To see the "declaration is definition" rule in effect, look at the following example that uses a vector:

      std::vector<int> v(10);
      v.push_back(1);
      v.push_back(5);
      // ...
    

The variable v is immediately initialized as a vector of 10 default elements. Then it can be operated on normally. There does not need to be an explicit assignment to the result of the constructor like in Java.

The new keyword is different

In Java, to create a new object, you have to use new ...;. As seen above, this is not the case in C++, where the new expression returns a pointer to the allocated object. See the following example:

      void makeVectors() {
        std::vector<int> v1(5);
        std::vector<int> *v2 = new std::vector<int>(5);
      }
    

The first statement constructs a new vector with automatic storage duration: it will be destroyed when the enclosing scope ends (at the end of makeVectors). The second statement constructs a new vector with dynamic storage duration: the object is stored on the heap, and thus it will not be cleaned up automatically. Even worse, the pointer v2 which points to the constructed vector is destroyed at the end of the function: a memory leak has occurred.

There is no garbage collector

When you allocate an object on the heap (with dynamic storage duration), you are advised to deallocate it at a later point, or else you will get memory leaks (as seen above). To clean up an object, use the delete statement:

      int sum(std::vector<int> *v) {
        int sum = std::accumulate(v->begin(), v->end(), 0);
        delete v;
        return sum;
      }
    

The vector is deallocated after the sum has been computed. (Side note: To access the iterator functions of v, we must use the dereferencing access operator ->.)

Obviously, this is very dangerous because other functions might still have a reference to v, which has been deleted. Therefore, it is much better to transfer ownership of the pointer to sum:

      int sum(std::unique_ptr<std::vector<int>> v) {
        int sum = std::accumulate(v->begin(), v->end(), 0);
        return sum;
      }
      
      int main() {
        std::unique_ptr<std::vector<int>> p = std::make_unique<std::vector<int>>(new ...);
        sum(std::move(p));
      }
    

This works because the unique_ptr has automatic storage and it will be dropped at the end of sum. The calling function (main) must be aware of this, and it will pass the unique pointer into the sum function using std::move. This is necessary because a unique_ptr is move-assignable, but not copy-assignable. After main has passed the pointer on, p will be empty; no memory has been leaked.

Classes are super-powered structs

When declaring a class, there are some special member functions that should be taken care of. All of the following will be implicitly defined if no alternatives are provided. To avoid this, you must manually define them as deleteddelete.

Now let's see the definition of special members in action:

      class Singleton {
       private:
          int value;
          Singleton(int v) : value(v) {};   // deletes default constructor
       public:
        getInstance() {
          static Singleton instance(5);
          return instance;
        }
        ~Singleton() {}   // could be omitted
        Singleton(const Singleton &p) = delete;
        Singleton(const Singleton &&p) = delete;
        Singleton & operator=(Singleton &p) = delete;
      }
    
Side note: Value categories

To survive the mess that is the C++ documentation, here is a short summary of _values: