How to specialize a class template?

Hello Community,

It seems, that this time I met my 'master' in this problem. Meaning, I do not have any clue how to approach this problem, and here is why.

-

Problem statement:
"In this chapter, the section Specialized Templates within Section 16.4 describes how to design templates that are specialized for one particular data type."

This sounds like there is a code example given in Section 16.4. That, if having paid even a little attention as to what is written, I should know how to do it. To remedy this suspicion upfront, here is the part that describes how it is done. First, the section contains 10 lines of text. Here cited is only the important part:

"For example, the declaration of a specialized version of the SimpleVector class might start like this:

class SimpleVector<char *>

The compiler would know that this version of the SimpleVector class is intended for the char * data type. Anytime an object is defined of the type SimpleVector<char *>, the compiler will use this template to generate the code."

-

The problem statement then goes on to say, (my own words: with this thurough knowledge):

"The section introduces a method for specializing a version of the SimpleVector class template so it will work with strings. Complete the specialization for both the SimpleVector and SearchableVector templates."

-

Lovely, isn't it? With a single line of code, that I don't even know how to use, it is my task now to specialize 2 template classes, consisting of more than 500 lines of code, so they exclusively work with that data type.

After trying for several hours of little progress working something out, trying things, I decided to start with the already finished classes, one of them at least, from scratch. Here is the code (not separated in header files) and driver program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
using std::cin;
using std::cout;

#include <iostream>

// The template class
template <class T>
class SimpleVector
{
private:
	T *aptr;
	size_t arrSize;

public:
	SimpleVector();
	SimpleVector(size_t size);

};

template <class T>
SimpleVector<T>::SimpleVector()
{}

template <class T>
SimpleVector<T>::SimpleVector(size_t sizeVal) : arrSize(sizeVal)
{}

// The specialized template working with c-strings
template<>
class SimpleVector<char *>
{
private:
	char *value;
	size_t arrSize;

public:
	SimpleVector<char *>::SimpleVector(char *v) : value(v)
	{
		std::cout << value << std::endl;
	}

	SimpleVector<char *>::SimpleVector(size_t sizeVal) : arrSize(sizeVal)
	{}
};

int main()
{
	const size_t SIZE = 2952;
	char *vSen { "「はじめからはじめるがよい。そして最後にくるまでつづけるのじゃ。そうしたらとまれ」" };
	char *vTrans{ "Begin at the beginning. Go on till you come to the end. Then stop." };
	
	SimpleVector<char *> l(SIZE);
	l = vSen;
	l = vTrans;

	return 0;
}


This works, but I have not the slightest clue if this is even right, meaning the constructor in the specialized class's constructor, ... So, are there any mistakes in there so far?

-

Also, here is part of the original class, which shall be modified, to give you an impression of what still need to be done, and how to approach modyfing the parts of that class in the specialized class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#ifndef SIMPLE_VECTOR_H_
#define SIMPLE_VECTOR_H_

#include <cstdlib>
#include <iostream>
#include <new>

template <class T>
class SimpleVector
{
	private:
		T	*aptr;
		int arraySize;
		int maxCapacity;

		void memError();
		void subError();

		template <class TPrint>
		friend std::ostream &operator << (std::ostream &, const SimpleVector<TPrint> &);

	public:
		SimpleVector();
		SimpleVector(int);
		SimpleVector(const SimpleVector &);
		~SimpleVector();

		// Mutator functions
		void push_back(const T &);
		void pop_back();

		// Accessor functions
		int getArraySize() const
		{ return arraySize; }
		
		T getElementAt(int);	
		T &operator [](const int &);
};


-

If you happen to have read this far, thank you if you did, I ask only for general guidance, a 'For Dummies' explanation in pseudocode, 'simple' function definitions in the specialized class, information of that nature.

[Edit:] It does not compile in cppshell, but works and does not give any warnings in VS, with Error-Level 4 set in the debugger.
Last edited on
So, are there any mistakes in there so far?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
rog.cc:38:24: error: extra qualification on member 'SimpleVector'
        SimpleVector<char *>::SimpleVector(char *v) : value(v)
        ~~~~~~~~~~~~~~~~~~~~~~^
prog.cc:43:24: error: extra qualification on member 'SimpleVector'
        SimpleVector<char *>::SimpleVector(size_t sizeVal) : arrSize(sizeVal)
        ~~~~~~~~~~~~~~~~~~~~~~^
prog.cc:50:15: error: ISO C++11 does not allow conversion from string literal to 'char *' [-Werror,-Wwritable-strings]
        char *vSen { "「はじめからはじめるがよい。そして最後にくるまでつづけるのじゃ。そうしたらとまれ」" };
                     ^
prog.cc:51:16: error: ISO C++11 does not allow conversion from string literal to 'char *' [-Werror,-Wwritable-strings]
        char *vTrans{ "Begin at the beginning. Go on till you come to the end. Then stop." };
                      ^
prog.cc:35:9: warning: private field 'arrSize' is not used [-Wunused-private-field]
        size_t arrSize;
               ^

(see https://wandbox.org/permlink/JcpEkLojleEyl72d )

otherwise, yes, it's a full specialization of SimpleVector<T> for T = char*
Alright, thank you! I fixed the errors ((quick fix, no errors now, no warning either) I tried both cppshell and this wandbox.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
using std::cin;
using std::cout;

// The template class
template <class T>
class SimpleVector
{
private:
	T *aptr;
	size_t arrSize;

public:
	SimpleVector();
	SimpleVector(size_t size);

};

template <class T>
SimpleVector<T>::SimpleVector()
{}

template <class T>
SimpleVector<T>::SimpleVector(size_t sizeVal) : arrSize(sizeVal)
{}

// The specialized template working with c-strings
template<>
class SimpleVector<char *>
{
private:
	char *value;
	size_t arrSize;

public:
	SimpleVector(char *v) : value(v)
	{
		std::cout << value << std::endl;
	}

	SimpleVector(size_t sizeVal) : arrSize(sizeVal)
	{}
};

int main()
{
	const size_t SIZE = 2952;
	char vSen[SIZE] { "「はじめからはじめるがよい。そして最後にくるまでつづけるのじゃ。そうしたらとまれ」" };
	char vTrans[SIZE] { "Begin at the beginning. Go on till you come to the end. Then stop." };
	
	SimpleVector<char *> l(SIZE);
	l = vSen;
	l = vTrans;

	return 0;
}


How would I now continue from there? (If what I have now is written correctly)

Maybe I should ask a more concrete question. What parts, given above second code-listing, do I have to rewrite? All of it? Meaning constructors, and functions? Or can things from that original template class be used? Like in an inheritance sort of way?
Last edited on
Online compiler does not like your:
36:2: error: extra qualification 'SimpleVector<char*>::' on member 'SimpleVector' [-fpermissive]
41:2: error: extra qualification 'SimpleVector<char*>::' on member 'SimpleVector' [-fpermissive]


Syntax:
https://www.geeksforgeeks.org/template-specialization-c/
http://en.cppreference.com/w/cpp/language/template_specialization


Templates provide compile-time polymorphism. Genericity.

That std::vector. It is just a dynamic array with plenty of syntactic sugar. No matter what the value_type is, the vector does the same things.


No. Wait! There is:
http://www.cplusplus.com/reference/vector/vector-bool/

A specialization that does some things differently. That does have members that the generic template does not have.


The SimpleVector has member aptr. If you don't have specialization and you do create:
SimpleVector<char*> foo;
Then T == char* and the type of foo.aptr is char**

In your specialized version the type of foo.aptr is char* (just like in SimpleVector<char>). As such, your specialization is different from the template, which is the purpose of the specialization.


The main question is, in which ways should the char* differ from all the other T?

In case of std::vector<bool> it is mainly about optimizing implementation when each element needs only one bit (but bool is bigger).
What parts, given above second code-listing, do I have to rewrite? All of it? Meaning constructors, and functions? Or can things from that original template class be used? Like in an inheritance sort of way?


Simple answer is "All of it". This is not inheritance. There is no relationship between SimpleVector<T> (for every T other than char*) and your special SimpleVector<char*>. You can fill SimpleVector<char*> with totally different data members and member functions, give it a bunch of base classes, etc.

Of course good programmers don't repeat themselves: a good exercise here is to derive both SimpleVector<T> and SimpleVector<char*> from a common base, and see how much common code and/or data you can put into that base.

(for example, the above-mentioned std::vector<bool> and std::vector<T> are both derived from __vector_base_common in LLVM's libc++ https://github.com/llvm-mirror/libcxx/blob/master/include/vector#L2148 though there wasn't a lot they could put in that common base)
Last edited on
keskiverto
Errors:


I fixed it in the second post after Cubbi kindly pointed them out to me. :)

keskiverto:
The SimpleVector has member aptr. If you don't have specialization and you do create:
SimpleVector<char*> foo;
Then T == char* and the type of foo.aptr is char**


How then would it have to look like, to make it different in the specialized version versus the original template?

keskiverto
The main question is, in which ways should the char* differ from all the other T?


I don't think that there should be any difference to the base class. The difference, with what little the problem statement has to offer is:

The classes in question should be specialized so, that they work exclusively with c-strings.

This can mean both:
1: Write specialized template classes. (The way I try it)
2: Specialize the classes, so they only work with c-strings.

I refer back to the relevant part of the problem statement:

"The section introduces a method for specializing a version of the SimpleVector class template so it will work with strings. Complete the specialization for both the SimpleVector and SearchableVector templates."
Last edited on
Cubbi

Simple answer is "All of it". This is not inheritance. There is no relationship between SimpleVector<T> (for every T other than char*) and your special SimpleVector<char*>. You can fill SimpleVector<char*> with totally different data members and member functions, give it a bunch of base classes, etc.

I almost 'feared' that this would be the answer.

Cubbbi
Of course good programmers don't repeat themselves: a good exercise here is to derive both SimpleVector<T> and SimpleVector<char*> from a common base, and see how much common code and/or data you can put into that base.


PLEASE, I beg you, have mercy, it is difficult enough as it is right now, with what limited knowledge as concerns template specialization. :)

Yes, of course I agree, that there should be no repetition. And there would not be any, if I were to find a way to alter the original class templates so, that they work solely with the char * data type. There would then be no duplication of code.

I have written, how many? 3 template classes, the SimpleVector (of which most code was provided, and only parts had to be modified to add functions that emulate push_back() and pop_back() vector class functionality. The Sortable and Searchable classes, doing what they say, then a variation in which the base-template class is SimpleVector and SearchableVector inherits from SortableVector (with additional function in the SearchableVector class).

Anyway, the point in short is, if there is the task to specialize the classes, whichever way ... and however it is done, there is no common base here.

Cubbi
(for example, the above-mentioned std::vector<bool> and std::vector<T> are both derived from __vector_base_common in LLVM's libc++ https://github.com/llvm-mirror/libcxx/blob/master/include/vector#L2148 though there wasn't a lot they could put in that common base)

^ scary, this. I have been skimming, and found some parts that are understood, but - frankly, this as an example, I would not wish to know what that common base class might look like, and how long it is. ;-)
Last edited on
exclusively with c-strings

Ok.

The SimpleVector has(?) an array of anything. Certain number of elements.

The SimpleVector<char*> has an array of char, but not just an array but a C-string. You can give it C-string and you can get a C-string from it.

How does C-string differ from array of chars? It has one extra element for terminating null.

There you have clear difference to implement.


Note that if the specialization is present, then the SimpleVector cannot store an array of char pointers ...
keskiverto
There you have clear difference to implement.


Putting it like this, yes, there is a difference. This, though, is a matter of detail, to which it seems right now I will never come to ...

What I have done in the meantime is trying to figure out a way to make some program in whatever direction and was unable to. I watched a course on Udemy, dealing with the specific topic of template specialization, tried to make heads and tails of tutorials found on the web, on blogs, dedicated programmers websites, etc.

It seems this problem is way above my current level of ability, since normally I can figure things out. But this time, all I see is problem upon problem. I feel that I can't progress without some concrete example that specifically deals with my situation.

I posted the header part of my original code as I have it. What I would ask is that it is 'specialized' to accept and work solely with c-strings. Eventually also an example of a ctor definition and function declaration, only the declaration. This, then, should hopefully do, to let me figure out the rest; Where I do foresee that there will then still be a million questions to ask ... Which remains to be seen ...

So, you, keskiverto, or someone who happens to read and wishes to help could do that? (While meantime little me is going on to hunt for resources, and keep trying and failing ...)
Last edited on
It seems this problem is way above my current level of ability

Seems unlikely.

Try first writing a separate class named SimpleVectorOfCStrings. Naturally, this particular container should be designed to handle C-strings. Since you've already got SimpleVector under your belt, this should be tractable.

If you can do that, all that's left is to change the name SimpleVectorOfCStrings to SimpleVector<char*>.

Optionally as @Cubbi suggests, if there is common functionality between SimpleVectorOfCStrings and SimpleVector, you could consider inheriting that functionality from a common base class.
Last edited on
mbozzi

This is what I have been trying all along. Maybe my last post stems from frustration, because all I saw was one problem after the other. In the meantime I re-read what keskiverto had pointed me to,

"There you have clear difference to implement."


It may sound stupid to confess that of course I learned how to work with c-strings, array of c-strings, but was unable to wrap my mind around to do something with it. All my focus was on: getting a start, and getting at least some output of some kind, to see whether what I have does something

In the meantime, after some medidation, and watching https://www.youtube.com/watch?v=s4hFXGiPjwI I started all over. Trimming down the SimpleVector class to the barebones basics. So far, I have this, and the code is a bit all over the place. (*Specific questions please see below)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#include <iostream>
#include <string.h>
#include <cstdlib>
#include <iostream>
#include <new>

/* **********************************************************
		SimpleVector class
  ********************************************************** */

template <class T>
class SimpleVector
{
	private:
		T  *aptr;
		int arraySize;

	public:
		SimpleVector();
        SimpleVector(int);
		SimpleVector(const SimpleVector &);

		// Mutator functions
		void push_back(const T &);
		void pop_back();

		// Accessor functions
		int getArraySize() const
		{ return arraySize; }
		
		T getElementAt(int);	
		T &operator [](const int &);
};

template<class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj)
{
	arraySize = obj.arraySize;

	aptr = new T[arraySize];

	if (aptr == 0)
	{
		return;
	}

	for (int count = 0; count < arraySize; count++)
	{
		*(aptr + count) = *(obj.aptr + count);
	}
}

template <class T>
SimpleVector<T>::SimpleVector(int arrSize)
{
	arraySize = arrSize;

	try
	{
		aptr = new T[arrSize];
	}
	catch (std::bad_alloc)
	{
		std::cout << "Error: Can't allocate memory ...\n";
	}

	for (int count = 0; count < arraySize; count++)
	{
		*(aptr + count) = 0;
	}
}

template <class T>
T &SimpleVector<T>::operator[](const int &subPos)
{
	if (subPos < 0 || subPos >= arraySize)
	{
		std::cout << "Error: Subscript out of range!\n";
	}

	return aptr[subPos];
}

template <class T>
std::ostream &operator << (std::ostream &strm, const SimpleVector<T> &obj)
{
	for (int count = 0; count < obj.arraySize; count++)
	{
		strm << (*(obj.aptr + count)) << ' ';
	}

	return strm;
}


/* **********************************************************
		Specialized class
  ********************************************************** */

template<>
class SimpleVector<const char *>
{
	private:
		char **stringPtr;
		int    arraySize;

		friend std::ostream &operator << (std::ostream &strm, const SimpleVector &obj)
		{
		    strm << "Meantime in your specialized template ...\n";
			for (int count = 0; count < obj.arraySize; count++)
			{
				strm << (*(obj.stringPtr + count)) << '\n';
			}

			return strm;
		}

	public:		
		SimpleVector(const char *charArr[], int arrSize) : arraySize(arrSize)
		{
			stringPtr = new char *[arrSize];

			if (stringPtr == 0)
			{
				std::cout << "ERROR: Insufficient memory!\n";
			}

			for (int i = 0; i < arrSize; i++)
			{
				int length = strlen(charArr[i]);

				stringPtr[i] = new char[length + 1];

				strcpy(stringPtr[i], charArr[i]);
			}
		}
};

/* **********************************************************
		Main function
  ********************************************************** */

int main()
{
	const int SIZE = 6;
	const char *data[] = { "one", "two", "freddy's", "coming", "for", "you ..." };
    
	int numels = sizeof(data) / sizeof(char *);

	std::cout << "This is the current content of the char array:\n";
	for (int i = 0; i < numels; i++)
	{
		std::cout << "Number # " << (i + 1) << ": "
			<< data[i] << "\n";
	}


	SimpleVector<const char *> newData(data, SIZE);

    std::cout << (newData);
    
	SimpleVector<int> simpleArr(SIZE);
	for (int x = 0; x < SIZE; x++)
	{
		simpleArr[x] = (x * 2);
	}

	for (int i = 0; i < SIZE; i++)
	{
		std::cout << simpleArr[i] << "\n";
	}

   return 0;
}


* This is how things stand right now. First of all, can you spot anything that is done wrong here? (So far, neither VS, nor wandbox, nor cppshell with all errors on, pedantic so forth do indicate any errors) The output is as expected, so, seemingly (and I write this with utmost caution) so far, it is done correctly.

How can i structure it all better? (Mind, that specialized class, template class, and driver, each reside in their own file of course).

Next, the code in both classes is a bit all over the place, but that aside, there is in the specialized class the ctor, and, to be honest, I do wish to separate declaration from implementation. Yet, when I try to only write the declaration of say the ctor in the specialized class, then try to write:

template <>

I don't seem to get the correct syntax. How would it have to look like, if everything else, so far, seems to be done correct in the code as is?

Thank you for any further help with this in advance, and for all the input you all have given me so far!
Last edited on
1. Your base template does not have destructor. Who does deallocate the arrays that the constructors allocate dynamically? Nobody. You do have a memory leak. Clear error.

You do need a destructor and then some. See the Rule of Three: http://en.cppreference.com/w/cpp/language/rule_of_three


2. operator[] does make a remark if you give invalid index, but what do you do on line 81? Happily use the index anyway. That could be an error.


3. In one of the constructors you do catch exception, but nowhere else. Be consistent.


4. You do use both notations foo[bar] and *(foo + bar). Be consistent.


5. You do specialize the T=const char *. const. An array of read-only strings. That is ok, but would it be fun to tackle the non-const version next?


6. Do you really need a separate operator<< for your specialization?
Befriending the operator<< could be enough.


7. The cppreference site has syntax examples (but mostly for more complex cases).
Last edited on
Misenna wrote:
So far, neither VS, nor wandbox, nor cppshell with all errors on, pedantic so forth do indicate any errors

not true:
wandbox with gcc https://wandbox.org/permlink/m0gyMK8cAvbdpYO0
1
2
3
4
5
6
7
8
prog.cc: In instantiation of 'SimpleVector<T>::SimpleVector(int) [with T = int]':
prog.cc:162:34:   required from here
prog.cc:62:2: warning: catching polymorphic type 'class std::bad_alloc' by value [-Wcatch-value=]
  catch (std::bad_alloc)
  ^~~~~
prog.cc: In function 'int main()':
prog.cc:165:16: warning: 'simpleArr.SimpleVector<int>::aptr' may be used uninitialized in this function [-Wmaybe-uninitialized]
   simpleArr[x] = (x * 2);


keskiverto pointed out a few errors, but you should learn about one more tool, valgrind. I don't know of an online service that runs it for you, you may need an actual computer with Linux on it. For your program as posted, it prints:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
==31179== HEAP SUMMARY:
==31179==     in use at exit: 108 bytes in 8 blocks
==31179==   total heap usage: 8 allocs, 0 frees, 108 bytes allocated
==31179==
==31179== 24 bytes in 1 blocks are definitely lost in loss record 1 of 3
==31179==    at 0x4C2800A: operator new[](unsigned long) (vg_replace_malloc.c:384)
==31179==    by 0x401199: SimpleVector<int>::SimpleVector(int) (demo.cc:60)
==31179==    by 0x400EAC: main (demo.cc:162)
==31179==
==31179== 84 (48 direct, 36 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3
==31179==    at 0x4C2800A: operator new[](unsigned long) (vg_replace_malloc.c:384)
==31179==    by 0x40103D: SimpleVector<char const*>::SimpleVector(char const**, int) (demo.cc:121)
==31179==    by 0x400E8A: main (demo.cc:158)
==31179==
==31179== LEAK SUMMARY:
==31179==    definitely lost: 72 bytes in 2 blocks
==31179==    indirectly lost: 36 bytes in 6 blocks


(this in particular is due to the missing destructors)

Also, an extra a note on the constructors:
one constructor says
1
2
	aptr = new T[arraySize];
	if (aptr == 0) 

That if(aptr == 0) will never happen, new cannot return null

the other constructor says:
1
2
3
4
5
6
7
8
9
10
11
12
13
	try
	{
		aptr = new T[arrSize];
	}
	catch (std::bad_alloc)
	{
		std::cout << "Error: Can't allocate memory ...\n";
	}

	for (int count = 0; count < arraySize; count++)
	{
		*(aptr + count) = 0;
	}

so if allocation failed, it complains and then continues on accessing unallocated memory and, if it didn't crash by some miracle, creates an unusable object. In C++, you should never ever create an unusable object, that's the core concept underpinning the whole language, RAII ( http://en.cppreference.com/w/cpp/language/raii ). Just let those exceptions go.

Your sized constructor should be simply
 
  SimpleVector(int sz) : aptr(new T[sz]), arraySize(sz) {}

or, if you want the entire array zeroed out on contsruction (it seems you do)
 
  SimpleVector(int sz) : aptr(new T[sz]()), arraySize(sz) {}

or, if you really want the message,
1
2
3
SimpleVector(int sz) try : aptr(new T[sz]()), arraySize(sz) {} catch(const std::bad_alloc& e) {
    std::cout << "Error: Can't allocate memory ...\n";
} 

but that may be considered a bit of advanced C++ (even though it's still boring old C++98)

speaking of which.. here's how I'd write the core part of that SimpleVector in today's C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template <class T>
class SimpleVector
{
 private:
  std::unique_ptr<T[]> a;
  int sz = 0;
 public:
  SimpleVector() = default;
  SimpleVector(int sz) : a{std::make_unique<T[]>(sz)}, sz{sz} {}
  SimpleVector(const SimpleVector &o) : a{std::make_unique<T[]>(o.sz)}, sz{o.sz} {
    std::copy(o.a.get(), o.a.get()+sz, a.get());
  }
  SimpleVector(SimpleVector&&) = default;
  ~SimpleVector() = default;
  SimpleVector& operator=(const SimpleVector& o) {
    if (this != &o) {
      if (o.sz != sz) {
        a = std::make_unique<T[]>(o.sz);
        sz = o.sz;
      }
      std::copy(o.a.get(), o.a.get()+sz, a.get());
    }
    return *this;
  }
  SimpleVector& operator=(SimpleVector&& o) noexcept {
    if(this != &o) {
      a = std::exchange(o.a, nullptr);
      sz = std::exchange(o.sz, 0);
    }
    return *this;
  }
};
Last edited on
keskiverto, thanks a million, you are correct with most everything you point out.

1. Your base template does not have destructor. Who does deallocate the arrays that the constructors allocate dynamically? Nobody. You do have a memory leak. Clear error.

You do need a destructor and then some. See the Rule of Three: http://en.cppreference.com/w/cpp/language/rule_of_three

3. In one of the constructors you do catch exception, but nowhere else. Be consistent.

True. Both the dtor as well as the exception, at its bare minimum, is incomplete in one case and does nothing at all, in the other it is non-existent. I could, and likely should have, included the dtor also, instead of only the constructor. (Will be fixed!)

2. operator[] does make a remark if you give invalid index, but what do you do on line 81? Happily use the index anyway. That could be an error.


This is also due to testing, that the actual function is not added that would catch this and exit the program.

5. You do specialize the T=const char *. const. An array of read-only strings. That is ok, but would it be fun to tackle the non-const version next?

Yes, I will do that, as soon as I have the other version going.

6. Do you really need a separate operator<< for your specialization?
Befriending the operator<< could be enough.

If this is possible I would rather opt for having it in the template class only, working for both. Would this be how it is done?

1
2
3
4
5
6
7
template <>
class SimpleVector<const char *>
{
     private:
        friend std::ostream &operator << (std::ostream &, const SimpleVector &);
       // The other code
}


7. The cppreference site has syntax examples (but mostly for more complex cases).

Thus renders it a less helpful resource for this particular case, for now at least. It seems that my best choice is this book that is specialized and teaches all about templates: C++ Templates: The Complete Guide (2nd Edition) by David Vandevoorde, Nicolai M. Josuttis, and Douglas Gregor to learn more about templates, and specialization of templates.
Cubbi, interesting. I swear that when I tried, there were no complaints in wandbox! (The reason is likely that I changed some bits of code before while posting, leading up to the warnings/errors you are pointing out).

Cubbi
keskiverto pointed out a few errors, but you should learn about one more tool, valgrind. I don't know of an online service that runs it for you, you may need an actual computer with Linux on it.

I do use an 'actual computer', and have both win 10 pro and Ubuntu and Slackware running on there. So, I will get valgrind and put it to good use, once its understood how this one works.


Also, an extra a note on the constructors:

...
so if allocation failed, it complains and then continues on accessing unallocated memory and, if it didn't crash by some miracle, creates an unusable object.

In this current version it does, in the original version, there is a function that catches that, calls a function that outputs an error message, and exits the program with an error code. So, the function is there, the error message is there, but not in the ctor.

There is also a fact that has not been mentioned, and that is, that the SimpleVector class is vanilla code, taken directly from my textbook, which in the first stage some behavior has been added, to give it the functionality of a real vector - push_back and pop_back. Since this is a major rework anyway, I will do what you suggested, and will change things around quite a bit. Ctor in the template and specialized template, some point down the road.

Also, I have to thank you very much for showing me some more modern version of how things can be done. 'Advanced stuff' or not, as long as it is understood and in some way explained, I am fast to pick up and learn. And what I do not understand, I try to learn and deepen my understanding of, so as not to run into the trap of saying: "Thank you, taking the code, but having no clue how to 'translate' and use it when it would be useful to use in other problems to which code in whole or part are a great/very good solution."

Without writing any more about it, thank you both once again! For now, I have lots to do, to correct code. I will post as soon as I have something to show for.
Misenna wrote:
I will get valgrind and put it to good use, once its understood how this one works

for the most basic use, just run it with your compiled program as the argument (and compile with debugging enabled and optimization disabled to see the exact lines of code that caused memory leaks):

1
2
$ g++ -g -O0 -Wall -Wextra -pedantic-errors -o test demo.cc
$ valgrind ./test


this helpfully says
==2368== Rerun with --leak-check=full to see details of leaked memory
so
$ valgrind --leak-check=full ./test
was the command I ran to get the report in the previous post.

Cubbi, just to let you know. I tested it, with the parameters you have given me, and it delivers the same result/errors. Though at the end of the report it tells me something slightly different:


==9898== For counts of detected and suppressed errors, rerun with: -v


Also the command is slightly different $ valgrind --leak-check=full -v ./main.cc in my case, this gave some insight as to what happens behind the curtain, which I will have to learn to interpret correctly. What the source of the errors is, you already pointed out (thank you once again!), which I will now start to work on to resolve, after these initial tests with valgrind.

Edit: $ valgrind -v ./main.cc does deliver the same output as $ valgrind --leak-check -v ./main.cc
Last edited on
I am now at the point where I would need guidance. It is the newly added push_back function, which now also uses unique_ptr, instead of raw pointers.

Besides that, I replaced the friend ostream operator that does the output with a print function. It makes more sense. The other version, seen in the first post, second listing, was more of an experiment anyway.

Since I do not have much experience with smart pointers, I would appreciate suggestions, correction, and most important: whether there are errors/mistakes having been made in the push_back() function. (I hope not, since valgrind does not find any memory-leaks, wandbox, at least when testing with various settings, does not throw any errors, neither does cppshell with all warnings turned on.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include <iostream>
#include <cstdlib>
#include <memory>

class ExceptionClass
{
	private:
		int errIndex;

	public:
		ExceptionClass(int subPos)
		{ 
			errIndex = subPos;
		}

		int getIndex() const
		{ return errIndex; }
};

template <class T>
class SimpleVector
{
	private:
		std::unique_ptr<T []> aptr;
		int arraySize;

	public:
		SimpleVector() 
		{ }

		SimpleVector(int);
		SimpleVector(const SimpleVector &);
		~SimpleVector()
		{ }

		void push_back(const T &);

		int getArraySize() const
		{ return arraySize; }

		void print() const;
		
		// Overloaded operator functions
		T &operator [](int);
		T &operator =(const SimpleVector &);
};

/* **********************************************************
			SimpleVector<T>::SimpleVector() : int
		 - Constructor
	Sets the size of the array and allocates memory.
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(int arrSize)
{
	arraySize = arrSize;
	aptr = std::make_unique<T []>(arrSize);
	
	for (int count = 0; count < arraySize; count++)
	{ 
		aptr[count] = T(); 
	}
}

/* **********************************************************
			SimpleVector<T>::SimpleVector() : const obj &
		 - Copy Constructor
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj) 
{
	arraySize = obj.arraySize;
	aptr = std::make_unique<T []>(arraySize);

	for (int count = 0; count < arraySize; count++)
	{ 
		aptr[count] = obj[count]; 
	}
}

/* **********************************************************
			T &SimpleVector<T>::operator=() : const obj &
		 - Overloaded assignment operator function
	********************************************************** */

template <class T>
T &SimpleVector<T>::operator=(const SimpleVector &right)
{
	if (this != &right)
	{
		if (right.arraySize != arraySize)
		{
			arraySize = right.arraySize;
			aptr = std::make_unique<T []>(right.arraySize);
		}
			
		for (int count = 0; count < arraySize; count++)
		{
			aptr[count] = right.aptr[count];
		}
	}

	return *this;
}

/* **********************************************************
			SimpleVector<T>::operator=() : T
	This function emulates the vector's push_back() function.
	It adds a new element at the end of the array, after its
	current last element.
	********************************************************** */

template <class T>
void SimpleVector<T>::push_back(const T &tempVal)
{
	std::unique_ptr<T []> tmpPtr = std::make_unique<T[]>(arraySize+1);

	for (int count = 0; count < arraySize; count++)
	{
		tmpPtr.get()[count] = aptr.get()[count];
	}

	tmpPtr.get()[arraySize] = tempVal;
	aptr = std::move(tmpPtr);
	
	++arraySize;
}

/* **********************************************************
			T &SimpleVector<T>::operator[] : int
		 - Overloaded [] operator function
	Returns a reference to the element in the array indexed by
	the subscript.
   ********************************************************** */

template <class T>
T &SimpleVector<T>::operator[](int subPos)
{
	if (subPos < 0 || subPos >= arraySize)
	{
		throw ExceptionClass(subPos);
	}

	return aptr[subPos];
}

/* **********************************************************
			SimpleVector<T>::print()
	Outputs the array's contents to screen.
   ********************************************************** */

template <class T>
void SimpleVector<T>::print() const
{
	for (int count = 0; count < arraySize; count++)
	{
		count > 0 && (count % 10 == 0) ? 
			std::cout << aptr[count] << std::endl : 
			std::cout << aptr[count] << " ";
	}
	std::cout << std::endl << std::endl;
}

int main()
{
	const size_t SIZE = 25;
	//int subPosition = 0;
	int addValue = 0;

	SimpleVector<int> intVect(SIZE);
	SimpleVector<double> dVect(SIZE);

	for (size_t i = 0; i < SIZE; i++)
	{
		intVect[i] = (i + 2);
		dVect[i] = (i * 2.4);
	}

	intVect.print();
	dVect.print();

	/*std::cout << "Enter a subposition (0 through " << SIZE << "): ";
	std::cin >> subPosition;*/

	std::cout << "Enter a value: ";
	std::cin >> addValue;

	try
	{
		intVect.push_back(addValue);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.print();
		// std::cout << "\n" << (intVect[subPosition]) << std::endl;
	}
	catch (ExceptionClass r)
	{
		std::cout << r.getIndex() << " out of range!" << std::endl;
	}

	std::cout << "\nTh ... th ... that's all, Doc!";
	
	return 0;
}
Last edited on
Good job embracing unique_ptr!

another constructor note, regarding
1
2
3
4
5
6
7
8
9
10
11
template <class T>
SimpleVector<T>::SimpleVector(int arrSize)
{
	arraySize = arrSize;
	aptr = std::make_unique<T []>(arrSize);
	
	for (int count = 0; count < arraySize; count++)
	{ 
		aptr[count] = T(); 
	}
}

check out
1. the syntax of a C++ constructor, in particular
http://en.cppreference.com/w/cpp/language/initializer_list
and https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rc-initialize

2. what std::make_unique does for arrays (hint: it writes a T() into every member)

you'll find that if all unnecessarily repeated code is removed from your constructor, it is precisely equivalent to
1
2
3
4
template <class T>
SimpleVector<T>::SimpleVector(int arrSize)
 : aptr( std::make_unique<T []>(arrSize) ), arraySize(arrSize) 
 { }


in fact, none of your constructors use the C++ constructor syntax (the one with a colon). Did you learn about them from a textbook written before 1985? (roughly - I am not sure when C++ got its syntax, but it was before 1990 for sure)

Cubbi, thank you, I try my best.

To answer your last question first. It is a little difficult. If I had to make a guess, the first revision of my book came out somepoint in the 90's in its first revision. So, there may well be some old code in the examples given, as may be the case in the text. Yet, there is also C++ 11 code there with extensive examples. The book is Starting Out With C++ From Control Structures through Objects 9th Edition. I started working with the 8th, then, around halfway in came the new edition and I opted for that.

-

I am aware of the constructor syntax, meaning initializer list. I have been using it in some cases, while in others the general 'old' constructor syntax is in use. Most often it is the old one. Of course I was not aware of the fact, that, by using this instead of the other, code duplication can be avoided. Now that I know it is clear that this is what will be used.

-

2: So, it is the same as writing:

Dice::Dice() : someArray{}
{ }

Which initializes an array's elements to 0 when constructor is called. If we assume that a simple array, not std::array, exists in a class?

And one general question, just to be sure, the code, leaving aside the constructor for a small second, in particular the push_back() function, it is correct as it is?

Edit: Found a small mistake, in the print function ... stupid me, the last value never gets output, even though it is added. That is fixed ...

-

So, here is the code of the first class, complete with all functions, and the changes to the constructor being added. Meaning it now contains a push_back() and pop_back() function, and it seems to go without a hitch. So, please, once again, if there is something I miss, and especially as concerns the two functions, are there any errors that I can't spot? Problems, something else? If no, I will go on working on the second class, and the first specialized version.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include <iostream>
#include <cstdlib>
#include <memory>

class ExceptionClass 
{
	private:
		int errIndex;

	public:
		ExceptionClass(int subPos) : errIndex(subPos)
		{ }

		int getIndex() const
		{ return errIndex; }
};

template <class T>
class SimpleVector
{
	private:
		std::unique_ptr<T []> aptr;
		int arraySize;

	public:
		SimpleVector() 
		{ }

		SimpleVector(int);
		SimpleVector(const SimpleVector &);
		~SimpleVector()
		{ }

		void push_back(const T &);
		void pop_back();

		int getArraySize() const
		{ return arraySize; }

		void print() const;
		
		// Overloaded operator functions
		T &operator [](int);
		T &operator =(const SimpleVector &);
};

/* **********************************************************
			SimpleVector<T>::SimpleVector() : int
		 - Constructor
	Sets the size of the array and allocates memory.
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(int arrSize) : 
	aptr(std::make_unique<T []>(arrSize)), arraySize(arrSize)
{ }

/* **********************************************************
			SimpleVector<T>::SimpleVector() : const obj &
		 - Copy Constructor
   ********************************************************** */

template <class T>
SimpleVector<T>::SimpleVector(const SimpleVector &obj) :
	aptr(std::make_unique<T []>(obj.arraySize)), arraySize(obj.arraySize)
{
	std::copy(obj.aptr.get(), obj.aptr.get() + arraySize, aptr.get());
}

/* **********************************************************
			T &SimpleVector<T>::operator=() : const obj &
		 - Overloaded assignment operator function
	********************************************************** */

template <class T>
T &SimpleVector<T>::operator=(const SimpleVector &right)
{
	if (this != &right)
	{
		if (right.arraySize != arraySize)
		{
			arraySize = right.arraySize;
			aptr = std::make_unique<T []>(right.arraySize);
		}
			
		for (int count = 0; count < arraySize; count++)
		{
			aptr[count] = right.aptr[count];
		}
	}

	return *this;
}

/* **********************************************************
			SimpleVector<T>::push_back() : T
	This function emulates the vector's push_back() function.
	It adds a new element at the end of the array, after its
	current last element.
	********************************************************** */

template <class T>
void SimpleVector<T>::push_back(const T &tempVal)
{
	std::unique_ptr<T []> tmpPtr = std::make_unique<T[]>(arraySize+1);

	for (int count = 0; count < arraySize; count++)
	{
		tmpPtr.get()[count] = aptr.get()[count];
	}

	tmpPtr.get()[arraySize] = tempVal;
	aptr = std::move(tmpPtr);
	
	++arraySize;
}
/* **********************************************************
			SimpleVector<T>::pop_back()
	This function emulates the vector's pop_back() function.
	As long as the array's size not is 0, an element will be
	removed. Otherwise an exception is thrown.
	********************************************************** */

template <class T>
void SimpleVector<T>::pop_back()
{
	if (arraySize != 0)
	{
		aptr[--arraySize];
	}
	else
	{
		throw ExceptionClass(arraySize);
	}
}

/* **********************************************************
			T &SimpleVector<T>::operator[] : int
		 - Overloaded [] operator function
	Returns a reference to the element in the array indexed by
	the subscript.
   ********************************************************** */

template <class T>
T &SimpleVector<T>::operator[](int subPos)
{
	if (subPos < 0 || subPos >= arraySize)
	{
		throw ExceptionClass(subPos);
	}

	return aptr[subPos];
}

/* **********************************************************
			SimpleVector<T>::print()
	Outputs the array's contents to screen.
   ********************************************************** */

template <class T>
void SimpleVector<T>::print() const
{
	for (int count = 0; count < arraySize; count++)
	{
		count > 0 && (count % 10 == 0) ? 
			std::cout << aptr[count] << std::endl : 
			std::cout << aptr[count] << " ";
	}
	std::cout << std::endl << std::endl;
}

int main()
{
		const size_t SIZE = 25;
	//int subPosition = 0;
	int addValue = 0;

	SimpleVector<int> intVect(SIZE);
	SimpleVector<double> dVect(SIZE);

	for (size_t i = 0; i < SIZE; i++)
	{
		intVect[i] = (i + 2);
		dVect[i] = (i * 2.4);
	}

	intVect.print();
	dVect.print();

	/*std::cout << "Enter a subposition (0 through " << SIZE << "): ";
	std::cin >> subPosition;*/

	std::cout << "Enter a value: ";
	std::cin >> addValue;

	try
	{
		intVect.push_back(addValue);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(935);
		intVect.push_back(235);
		intVect.push_back(236);
		intVect.print();

		for (int i = intVect.getArraySize(); i > 0; i--)
		{
			intVect.pop_back();			
		}
		std::cout << "Your array is now empty ...\n";
		
		// std::cout << "\n" << (intVect[subPosition]) << std::endl;
	}
	catch (ExceptionClass r)
	{
		std::cout << r.getIndex() << " out of range!" << std::endl;
	}

	std::cout << "\nTh ... th ... that's all, Doc!";
	
	return 0;
}
Last edited on
Topic archived. No new replies allowed.