Little wonders of C++ (1)

Please note: This article is only available in English.
C++ is transforming into a better language, that is even more powerful than before and much easier and robust to write.

This is the start of a new column that explores some of the best parts of C++. Of course a huge part will deal with C++11, but also C++14 will be presented or even old parts (C++98 or C++03) if necessary.

Each blog post will contain 5 techniques for better coding using C++. Some parts will be smaller, some will be more detailed. The first part will mainly deal with C++11.

The final keyword

Finally C++ allows programmers to seal classes. In C# we can do that by placing an additional modifier called sealed on a class. In Java we achieve the same thing by adding the finalmodifier. In C++ this is possible by placing an additional final after the name of a class.

Example:

class Foo final : public BaseFoo {
};

In my opinion every class that should be an endpoint has to be sealed. This makes it easy to spot such endpoints. Also it is likely (yet not implemented very often) that the compiler might be able to do some further optimizations.

The override keyword

The same is true for the override keyword. In the past C++ programmers usually kept annotating overridden methods as being virtual, such that other programmers could easily see that the given method is placed in a vtbl and that it might behave polymorphic. However, a great disadvantage is that no one was able to detect instantly if the given method is overriding a base class method or not. Maybe it was the initial declaration of the particular method.

This is now obsolete! From now on every C++ programmer should drop the virtual on overriding and follow the path shown in the next example:

class BaseFoo {
public:
  virtual ~BaseFoo() { }

  virtual void foo() { }
};
class Foo final : public BaseFoo {
public:
  void foo() override { }
};

Hence the usage is quite similar to the final declaration of a class. We just place the keyword after the signature.

Use shared_ptr and vector

I cannot emphasize enough that C++ is a stack based language and should be treated as such. Dealing with raw pointers in C++ does not only feel C-like, but is also the cause for most errors. There are alternatives that already perform the RAII pattern (more on that in another post) with raw pointers out of the box. If we want to allocate a dynamic array, we should use the std::vector class. No need to use delete[].

On the other side if we want to create objects on the heap we should use the make_shared function returning a std::shared_ptr instance. There are various other possibilities such as std::unique_ptr, however, this one is the most general and versatile in my opinion.

The single best thing on allocating pointer management classes on the stack. The need for defining who is responsible for deletion is obsolete. This is huge, because it eliminates both - the most common origin of a memory leak and the most common origin of a segmentation fault.

The unified constructor

Construction in C++ looks funny. Since we use / should use almost exclusively (of course there are a lot of exceptions to this rule) stack based creation, we will call the constructor in a similar fashion as we define prototype functions. If we do not supply any arguments, this is exactly the same thing as a forward declaration of a function. The main problem with the similarity is the most vexing parse scenario, which might occur in some situations.

There are other reasons why round brackets for calling the constructor is a bad practice - not only for calling the default constructor. Consider the following examples of initializing an integer variable:

int i; //undefined
int i(); //most vexing parse... forward declaration resulting in i = 1 / usually.
int i(5.3); //ouch, will be 5 without any warning
int i = 5.3; //also implicitly casted

However, using the new unified constructor most of those problems are gone automatically. Consider:

int i { }; //i is 0
int i { 5.3 }; //Narrowing exception

The uniform constructor does not allow narrowing. Additionally we can have the same syntax to use the initializer list, filling very simple data-transfer objects and calling usual constructors.

The move constructor

Since C++ is stack based we have to use copy operations quite often. In heap based languages / data types we only have to copy the pointer to the resource, while in C++ we make a copy of the full object. Of course this is not very efficient. A way out of this mess is to have a bit-flag that is set before one wants to move the content of an object. Then the copy constructor might not make a copy of the content (which could be the copy of a large array that is allocated on the heap), but instead just move the resource from the source to the target object.

This sounds like a lot of work for such a simple and reoccurring scenario. And indeed it was. Therefore most programmers searched for other ways and usually ended up with either self modifying operations (e.g. a += b instead of c = a + b), or by using heap allocated resources in the beginning (returning pointers, that end up having the same problem as usual, no one is responsible for deleting them).

Finally C++11 introduced the move constructor and move semantics. Every container in the STL uses the move constructor, i.e., we can be sure that STL created local variables will be moved when returning them. This is as efficient as it could be and should be implemented for any larger object.

template<typename T>
class List {
public:
	List(std::initializer_list<T> elements) :
		length(elements.size()) {
		values = new T[length];
		auto start = elements.begin();

		for (int i = 0; i < length; i++) {
			values[i] = *start;
			start++;
		}
	}
	List(const List& otherList) :
		length(otherList.length) {
		values = new T[length];

		for (int i = 0; i < length; i++)
			values[i] = otherList.values[i];
	}
	List(List&& otherList) :
		length(otherList.length),
		values(otherList.values) {
			otherList.values = new T[0];
			otherList.length = 0;
	}
	~List() {
		delete[] values;
	}
private:
	int length;
	T* values;
};

The move constructor takes an RValue reference object, denoted by the double ampersand. Here it basically copies the other lists contents to this instance and makes it impossible for the other list to delete these contents. This is a move.

Created . Last updated .

References

Sharing is caring!