Quote:
PtrVector<int> my_vector;
my_vector = 5; // not allowed thanks to the 'explicit' keyword
Almost, but a little misleading. It is operator=() that handles this case. The implicit constructor call to create the parameter to operator=() is what causes this to fail. A more straightforward example of how explicit works is like so:
PtrVector<int> my_vector = 5;
@OP
Implicit constructors have their uses, for example it is very convenient to initialise a std::string instance from a string literal. Outside such "basic" types, it is generally a good idea not to call constructors implicitly.
That said, the "explicit" keyword isn't often used to enforce this, probably because most complex types have constructors with more than a single argument. But its a good idea to be aware of "explicit" in case you get this error later on.
Quote:
I am rather confused by how there is no typical a = b syntax going on. I don't really get why we have private variable names and then just () brackets straight after them.
In C++, initialiser lists are preferred to assignment in the constructor body. One reason is that it is the only way to initialise const members, reference members and members with no default constructor. It is also the only way to specify parameters to any base class constructors. If you do not write an initialiser list,
all members will be default initialised before the constructor body runs.
Consider a complex type, like std::string, as a member. There are a few ways to implement std::string, but let us assume that it always allocates a character array to hold its data (this is one of the simplest implementations). Look at the following code:
class Example{public: Example() { text = "Hello, World"; }private: std::string text;};
With such a std::string implementation, this will involve a call to the string default constructor, which involves an allocation. Then, a temporary string will be created for "Hello, World", which involves another allocation. The strings assignment operator will be invoked, which will allocate again, copy the string data, then deallocate the first allocation. Finally, the temporary will be destroyed, another deallocation.
Compare with:
class Example{public: Example() : text("Hello, World") { }private: std::string text;};
This constructor call involves a single call, which will only allocate once. There are no deallocations in the constructor.
For complex types, you should really use the initialiser list. Another "feature" of initialiser lists is that the compiler knows that it can only initialise members. This means that you can use the same names for the parameters as the member variables. Some people dislike this, and I understand, but it is convenient:
template <typename T>class PtrVector {public: explicit PtrVector(size_t capacity) : buffer(new T *[capacity]), capacity(capacity), size(0) {}private: T **buffer; size_t capacity; size_t size;};
The code will work, the capacity member will be set to the input value and the buffer will be allocated with the parameters "capacity", not the (as yet unset) member value.