Interesting Class Declaration

Started by
26 comments, last by adder_noir 13 years, 6 months ago
Hi,

I've hit the latest snag in my book and seeing as I don't have a C++ tutor to hand I thought I'd come on here and ask a few questions. Here's the code:

template <typename T>class PtrVector {  public:    explicit PtrVector(size_t capacity)      : buf_(new T *[capacity], cap_(capacity), size_(0) {}  private:  T **buf_; // ptr to array of ptr to T  size_t cap_; // capacity  size_t size_; // size};


The bit I really don't get at the moment is this line:

explicit PtrVector(size_t capacity)      : buf_(new T *[capacity], cap_(capacity), size_(0) {}


This *looks* to me like a constructor which takes one argument with variable name 'capacity'. I am interested in the fact that the variable type size_t is not declared until later though inside the class definition. Is this legal? Can you declare a variable type as an argument to a constructor when that variable type is only defined inside the class?

I do have more questions but I'd like to work through this one first. Thanks.
Advertisement
It might be that your definition of size_t is identical to the typedef given to you by the standard, and that somehow the definition of the size_t alias creeped into your #include-hierarchy (in fact, it must [should (*)] be like that, as you are using size_t inside the class declaration already)


(*) -> "should" instead of "must", because I hear that (at least some version of) MSVC has problems implementing two phase lookup properly.
I see, thanks for the reply. So I am not incorrect to wonder where it came from then. Your post is quite technical you obviously know alot about this stuff, but from I gleaned from it it appears that size_t might already exist or be assumed to exist prior to the class definition.

My next questions also concern the same line:

explicit PtrVector(size_t capacity)      : buf_(new T *[capacity], cap_(capacity), size_(0) {}


1)Now buf_, cap_ and size_ are all private variables within the class. So am I correct to assume that this constructor is just initialising these variables. 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. For example what does cap_(capacity) do? cap_ is a private data member not a function so this syntax is strange to me. Given that buf_ is defined as:

T **buf_;

I can see that buf_(new T *[capacity],... is declaring a new array of pointers to variable type T of size capacity. That bit's ok. But not the rest.

2)What does the keyword explicit do? I get the feeling this is something I've already learned but forgotten about classes and constructors :o)

Just out of interest when he comes to implement stuff he does this, just one line for the whole class:

PtrVector<Shape> pic2(MAX);


Where 'Shape' I believe is an existing class or variable of some kind. MAX is obviously a pre-defined constant. I've never seen a class instantiated as an object using that syntax before except when using the std::vector type. Up to now I've only seen it as:

myClass newclass


So this is new to me. I'm guessing that the keyword 'explicit' has altered the way you create this PtrVector class from a simple beginner's style to a more advanced style which requires the <> brackets.

That's enough for now if I post too much I'll lose track of myself. Any input would be greatly appreciated ;o)
1. Yes you are correct. That syntax is known as an initializer list.

2. The explicit keyword tells the compiler that the PtrVector constructor cannot be called like this:

PtrVector<int> my_vector;my_vector = 5; // not allowed thanks to the 'explicit' keyword


Also, the PtrVector class is defined as a 'template' class. That's where the ClassName<Type> syntax is coming from. 'T' becomes the type that you insert between the < and > throughout the class's definition.

So essentially, when you write this:

PtrVector<int> my_vector(5);


You're creating an instance of PtrVector which can store 5 integers. So this:

PtrVector<Shape> my_vector(5);


Creates an instance of PtrVector which can manage 5 'Shape' objects - and yes, 'Shape' must be a predefined class or struct.
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.
Wow they're good replies! Many thanks to you both. I've just got back from the gym doing some ab exercises, I'll chew through those replies later and no doubt will return with questions for the next exciting installment of my proper education :oD
Ok I've had a good read of all this stuff and I've condensed it down to the following points/questions.

1)An initialiser list is a good confusion free, well structured and efficient way of initialising data members and avoiding potentially unwanted default initialisation and excessive hidden allocation operations.

2)The template part:

template <typename T> (I notice the absence of ';' here)

is responsible for causing the constructor to require the variable type inside the <> brackets:

PtrVector<variable type> classInstanceName;

3)The explicit part seems I think (and this is a question not an observation) to be the part which requires the = operator to be used with a number/value to set the constructor argument to, thus producing this syntax:

PtrVector<int> my_vector = 5;

I'd appreciate, if possible, some feedback on this thanks ;o)
Quote:Original post by adder_noir
Ok I've had a good read of all this stuff and I've condensed it down to the following points/questions.

1)An initialiser list is a good confusion free, well structured and efficient way of initialising data members and avoiding potentially unwanted default initialisation and excessive hidden allocation operations.
Indeed.
Quote:
2)The template part:

template <typename T> (I notice the absence of ';' here)

is responsible for causing the constructor to require the variable type inside the <> brackets:

PtrVector<variable type> classInstanceName;
Not exactly. A template is like a recipe for creating a class. The recipes must have the parameters specified in their definitions. When you actually create the class, you replace the formal template parameters with actual arguments. So, when you write a line like
PtrVector<Shape> pic2(MAX);
you're declaring a variable of type class PtrVector<Shape> and the definition of that class is implicitly generated from the template you provided.

So, it's not exactly true that the template is responsible for causing the constructor to require the angle brackets so much as the syntax of a template definition requires it.
Quote:
3)The explicit part seems I think (and this is a question not an observation) to be the part which requires the = operator to be used with a number/value to set the constructor argument to, thus producing this syntax:

PtrVector<int> my_vector = 5;

Yeah, that's sorta true in a trivial way. The implicit keyword was added to the language to explicitly disable implicit type conversions. Without it, a lot of programmers might write something like this.
  #include <iostream>  #include <string>  class Example1  {  public:    Example1(int i)    { std::cerr << "example 1i: " << i << "\n"; }    Example1(const std::string& s)    { std::cerr << "example 1s: " << s << "\n"; }  };  int main(int, char*[])  {    Example1 x("hello");  }

and then spend a lot of time in the WTF mode of debugging. Can you figure out why?

Stephen M. Webb
Professional Free Software Developer

I'm going to run that code through my compiler and see if I can figure it out. It looks to me like overloading the constructor function, which is for some reason rejected by the compiler or wrecks something at runtime.

Very good reply thanks, I'll check back when I've had a chance to try it out in code blocks.
Interesting. This actually ran in my compiler:

#include <iostream>#include <string>class Example1{    public:        Example1(int i)        { std::cout << "example 1i: " << i << "\n"; }        Example1(const std::string& s)        { std::cout << "example 1s: " << s << "\n"; }};int main(){    Example1 x("hello");    return 1;}


I have no idea why though. It also chose the correct constructor when I inputted an integer as the parameter for x's constructor. Seems I have much more to understand, or maybe I have a bad compiler. I'm betting this wouldn't have run is MS Visual. Seeing how some compiler's can allow bad stuff past is perhaps all the more reason to learn code properly as I am now!

Any ideas why it ran? I would have thought for all the world my compiler would have rejected an overloaded constructor function but it didn't. Frightens me to be honest, makes me realise how careful you have to be.

This topic is closed to new replies.

Advertisement