My simple vector, myVec

Pages: 12
@seeplus: Thanks.

1) Why have you used std::swap for copy/move assignment and move ctr instead of ordinary code, please? "Swap" means to exchange something for another but in assignment we won't exchange the two objects; we copy the right hand one to the left hand one.
As well as why have you defined a new template (U) for it, can't we just use T for that? (It's a member function of the class), please?

2) Why do we need to use friend and a new template for the operator<< too?

3) You've implemented the ordinary constructor this way:
myVec<T>::myVec(size_t size) : sz(size), elem(new T[size] {})
with a pair of parentheses for both sz and elem, but in Initializer list constructor and Copy Constructor you've used a pair of curly brackets for them:
1
2
myVec<T>::myVec(const myVec& arg) : sz {arg.sz}, elem {new T[sz]}
myVec<T>::myVec(size_t size) : sz(size), elem(new T[size] {})

Why? Will you explain this, please.

4)On line 106 can't we copy the vector this way:
myVec<int> vi4 = vi3;
instead of
myVec<int> vi4 {vi3};


@JLBorges: I thought about your point that the array is once initialised and another time assigned to, but all the copy constructors I've explored have been that way, hence yet I've not found the answer, although will think about it more.
Last edited on
1) swap is exchanging the member variables. For copy assignment, create a temp copy of the rhs using copy constructor. Then swap the elements of the tmp with those of this. Then this is a copy of the rhs ie lhs = rhs. Similar for move except just swap this and rhs. For move assignment, alos just do the swap and the destruction of the temp (which now holds the values from this [lhs]) will deal with releasing memory et al.

2) operator<< needs to be in the global namespace - not part of the class. It references private members so needs to be a friend of the class.

3) No reason. In C++ you can accomplish the same different ways. I wasn't aware I had.

4) Use both. Using {} is the modern good practice way.

Whole papers have been written about the available different ways a variable can now be initialised!
Last edited on
This is the kind of answer which I take advantages of it highly, each answer for its question numbered, thanks.

I extended the program to be the following: (still haven't used swap but will) - added resize, reverse and puch_back methods and a data member size_t space.

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 <stdexcept>

template <typename T>
class myVec {

public:
	myVec() { std::cout << "Default constructor is called.\n"; } // Default constructor
	myVec(size_t); explicit // Ordinary constructor
	myVec(const std::initializer_list<T>&); explicit // Initializer list constructor
	myVec(const myVec&);  // Copy Constructor
	myVec<T>& operator=(const myVec&);  // Copy Assignment
	myVec(myVec&&);  // Move Constructor
	myVec<T>& operator=(myVec&&);  // Move Assignment

	T& operator[](int n) { return elem[n]; } // for non-const vectors
	const T& operator[](int n) const { return elem[n]; }  // for const vectors

	~myVec() { std::cout << "Destructor is called.\n"; delete[] elem; }  // Destructor - Release recources

	auto size() const { return sz; }

	template <typename U>
	void swap(myVec<U>& sp) { std::swap(sz, sp.sz); std::swap(elem, sp.elem); }

	template<typename U>
	friend std::ostream& operator<<(std::ostream& os, const myVec<U>& vec);

	void reserve(const size_t);
	void resize(const size_t);
	void push_back(T);

private:
	size_t sz{};
	size_t space{};
	T* elem{};
};

//******************************************************************

template<typename U>         // the '<<' operator overloading for the vector
std::ostream& operator<<(std::ostream& os, const myVec<U>& vec)
{
	for (size_t i = 0; i < vec.sz; i++)
		os << vec.elem[i] << " ";

	return os;
}
//******************************************************

template <typename T>                           // Ordinary constructor
myVec<T>::myVec(size_t size) : sz{ size }, space{ size }, elem{ new T[size] }
{
	std::cout << "Ordinary constructor is called.\n";
	//elem.reset(new T[size]);
}

//************************************************************

template<typename T>   // Initializer list constructor
myVec<T>::myVec(const std::initializer_list<T>& lst) : sz{ lst.size() }, space{ lst.size() }, elem{ new T[sz] }
{
	std::copy(lst.begin(), lst.end(), elem);
	std::cout << "Initializer list constructor is called.\n";
}

//********************************************

template<typename T>                 // Copy Constructor
myVec<T>::myVec(const myVec& arg) : sz{ arg.sz }, space{ arg.sz }, elem{ new T[sz] }
{
	std::copy(arg.elem, arg.elem + arg.sz, elem);
	std::cout << "Copy constructor is called.\n";
}

//***************************************************

template<typename T>           // Copy Assignment
myVec<T>& myVec<T>::operator=(const myVec& arg)
{
	if (this == &arg) return *this;

	if (arg.size() <= space) {
		std::copy(arg.elem, arg.elem + arg.sz, elem);
		sz = arg.sz;
		return *this;
	}

	T* p = new T[arg.sz];
	std::copy(arg.elem, arg.elem + arg.sz, p);
	delete[] elem;
	elem = p;
	sz = arg.sz;
	space = arg.space;
	std::cout << "Copy assignment is called.\n";
	return *this;
}

//****************************************

template<typename T>        // Move constructor
myVec<T>::myVec(myVec&& arg)
{
	sz = arg.sz; 
	elem = arg.elem;
	space = arg.space;
	arg.sz = 0;
	arg.space = 0;
	arg.elem = nullptr;
	std::cout << "Move constructor is called.\n";
}

//******************************

template<typename T>                // Move assignment
myVec<T>& myVec<T>::operator=(myVec&& arg)
{
	if (this == &arg) return *this;

	elem = nullptr;
	sz = arg.sz;
	space = arg.space;
	elem = arg.elem;
	arg.sz = 0;
	arg.space = 0;
	arg.elem = nullptr;
	std::cout << "Move assignment is called.\n";
	return *this;
}

//*******************************************************

template<typename T>    // The reserve function
void myVec<T>::reserve(const size_t newalloc)
{
	if (newalloc <= space) return;
	T* p = new T[newalloc];
	std::copy(elem, elem + sz, p);
	delete[] elem;
	elem = p;
	space = newalloc;
}

//*********************************

template<typename T>   // The resize function
void myVec<T>::resize(const size_t newsize)
{
	reserve(newsize);
	for (size_t i; i < newsize; ++i) elem[i] = 0;
	sz = newsize;
}


//******************************************

template<typename T>       // The push back function 
void myVec<T>::push_back(T d)
{
	if (space == 0)
		reserve(8);
	else if (sz == space) 
		reserve(2 * space);

	elem[sz] = d;
	++sz;
}

//***********************************************

int main() try
{
	myVec<int> vi1;
	myVec<int> vi2{ 10 };
	myVec<int> vi3{ 2, 3, 4, 5 };
	myVec<int> vi4{ vi3 };
	
	vi1.push_back(12);
	vi1.push_back(14);
	
	vi3[3] = vi1[1];

	std::cout << vi1 << '\n';
	std::cout << vi2 << '\n';
	std::cout << vi3 << '\n';
	std::cout << vi4 << '\n';

	vi3 = myVec<int>{ 20 };
	auto vi5 = myVec<int>{ 30 };

	std::cout << vi3 << '\n';
	std::cout << vi5 << '\n';

	std::cin.get();
	return 0;
}

catch (std::invalid_argument& e)
{
	std::cerr << e.what() << "\n";
	abort();
}
catch (std::bad_alloc& e)
{
	std::cerr << e.what() << "\n";
	abort();
}
catch (...)
{
	std::cerr << "Something went wrong\n";
	abort();
}


1) please tell me if this is correct and looks fine based on C++17.

2) On line 176, when I use:
myVec<int> vi4 = vi3 ;
instead of
myVec<int> vi4{ vi3 };
I get two errors:
initializing': cannot convert from 'myVec<int>' to 'myVec<int>
class "myVec<int>" has no suitable copy constructor

why?

3) If we add delete[] p (since it's not needed anymore, say, on the line 95, it won't be innocent because both p and elem point to the same address and if we delete p the memory will be freed for both elem and p, making elem useless, right?

Last edited on
That you are now reserving memory is good.

But, in the reserve function, T* p = new T[newalloc]; default constructs newalloc objects
and then, std::copy(elem, elem + sz, p); assigns to (some of the) already constructed items.
What this implies: if the current size and capacity (space) of the vector is five, and we call reserve(1000), the size would remain unchanged, but 995 additional objects would be default constructed.

Ideally, the unused portion of the reserved area should contain uninitialised storage; all that is required is that there should be enough free space to grow up to a size of newalloc without reallocation.

Here is an example of a partial implementation (just enough for exercising reserve):

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
#include <iostream>
#include <iterator>
#include <memory>
#include <cstdlib>
#include <type_traits>
#include <initializer_list>

namespace my
{
    template < typename T > struct vector
    {
        using value_type = T ;
        using size_type	= std::size_t ;
        using difference_type =	std::ptrdiff_t ;
        using reference	= value_type& ;
        using const_reference = const value_type& ;
        using pointer = value_type* ;
        using const_pointer	= const value_type* ;
        using iterator = pointer ;
        using const_iterator = const_pointer ;
        using reverse_iterator = std::reverse_iterator<iterator> ;
        using const_reverse_iterator = std::reverse_iterator<const_iterator> ;

        bool empty() const noexcept { return size() == 0 ; }
        size_type size() const noexcept { return sz ; }
        size_type capacity() const noexcept { return cap ; }

        template< typename INPUT_ITERATOR > vector( INPUT_ITERATOR begin, INPUT_ITERATOR end )
        { for( ; begin != end ; ++begin ) push_back(*begin) ; }

        template < typename U > vector( std::initializer_list<U> ilist ) : vector( ilist.begin(), ilist.end() ) {}

        ~vector() { if(first) { destroy( first, size() ) ; deallocate(first) ; } }

        // TO DO: copy, assign, swap, move

        iterator begin() noexcept { return first ; }
        iterator end() noexcept { return begin() + size() ; }
        const_iterator begin() const noexcept { return first ; }
        const_iterator end() const noexcept { return begin() + size() ; }
        const_iterator cbegin() const noexcept { return first ; }
        const_iterator cend() const noexcept { return begin() + size() ; }

        reverse_iterator rbegin() noexcept { return reverse_iterator( end() ) ; }
        reverse_iterator rend() noexcept { return reverse_iterator( begin() ) ; }
        const_reverse_iterator rbegin() const noexcept { return reverse_iterator( end() ) ; }
        const_reverse_iterator rend() const noexcept { return reverse_iterator( begin() ) ; }
        const_reverse_iterator crbegin() const noexcept { return reverse_iterator( end() ) ; }
        const_reverse_iterator crend() const noexcept { return reverse_iterator( begin() ) ; }

        void push_back( const T& v )
        {
            reserve( size() + 1 ) ;
            construct( first+size(), v ) ;
            ++sz ;
        }

        void push_back( T&& v )
        {
            reserve( size() + 1 ) ;
            construct( first+size(), std::move(v) ) ;
            ++sz ;
        }

        template < typename... CTOR_ARGS > void emplace_back( CTOR_ARGS&&... args )
        {
            reserve( size() + 1 ) ;
            construct( first+size(), std::forward<CTOR_ARGS>(args)... ) ;
            ++sz ;
        }

        // TO DO: pop_back, lots more stuff

        void reserve( size_type requested_cap )
        {
            if( requested_cap > cap )
            {
                const auto n = recommended_capacity(requested_cap) ;

                // allocate uninitialised storage for n items
                T* new_buff = allocate(n) ;

                if( first != nullptr )
                {
                    // move construct / copy construct items in uninitialised storage
                    // move only if the move-constructor is guaranteed not to throw
                    // TO DO: support the basic guarantee for exception safety see: https://www.stroustrup.com/3rd_safe.pdf
                    if constexpr(CAN_MOVE_T)
                        std::uninitialized_copy_n( std::make_move_iterator(first), size(), new_buff ) ;
                    else std::uninitialized_copy_n( first, size(), new_buff ) ;

                    destroy( first, size() ) ; // destroy moved from / copied from items
                    deallocate(first) ; // release old buffer
                }

                // use new_buf
                first = new_buff ;
                cap = n ;
            }
        }

        private:

            size_type sz = 0 ;
            size_type cap = 0 ;
            T* first = nullptr ;

            static constexpr std::size_t T_SIZE = sizeof(T) ;
            static constexpr std::size_t T_ALIGN = alignof(T) ;
            static constexpr bool CAN_MOVE_T = std::is_nothrow_move_constructible_v<T> ;

            size_type recommended_capacity( size_type requested_cap ) const noexcept
            {
                // TO DO: fine tune as required

                size_type rec_sz = sz * 2 ; // default: small allocation; double current size

                if( size() * T_SIZE > 64 * 1024 ) rec_sz = sz * 4 / 3 ; // large allocation; increase by 50%
                else if( size() * T_SIZE > 16 * 1024 ) rec_sz = sz * 7 / 3 ; // medium allocation; increase by 75%

                // return larger of these (we always allocate space for at least 16 items).
                return std::max( std::max( requested_cap, rec_sz ), size_type(16) ) ;
            }

            T* allocate( size_type n ) // allocate uninitialised storage
            {
                // std::aligned_alloc: https://en.cppreference.com/w/cpp/memory/c/aligned_alloc
                return static_cast<T*>( std::aligned_alloc( T_ALIGN, T_SIZE * n ) ) ;
            }

            // invariant: buffer is a pointer returned earlier by allocate
            void deallocate( T* buffer ) { std::free(buffer) ; }

            // in-place construct at location pointed to by where
            T* construct( T* where, const T& v ) { return ::new (where) T(v) ; } // copy construct
            T* construct( T* where, T&& v ) { return ::new (where) T( std::move(v) ) ; } // move construct
            template < typename... CTOR_ARGS > T* construct( T* where, CTOR_ARGS&&... args ) // contruct from args
            { return ::new (where) T( std::forward<CTOR_ARGS>(args)... ) ; }

            void destroy( T* p ) { p->T::~T() ; } // destroy without releasing memory
            void destroy( T* beg, size_type n ) { for( size_type i = 0 ; i < n ; ++i ) destroy(beg+i) ; }
    };
}

#include <string>
#include <algorithm>

int main()
{
    const auto print = []( const auto& seq ) { for( const auto& v : seq ) std::cout << v << ' ' ; std::cout << '\n' ; };

    my::vector<std::string> vec { "how", "now", "brown", "cow" } ;
    vec.emplace_back( 4, 'd' ) ;
    print(vec) ;

    std::sort( std::begin(vec), std::end(vec) ) ;
    print(vec) ;

    while( vec.size() < 20 ) vec.emplace_back() ; // to test reserve
    std::cout << vec.size() << ' ' << vec.capacity() << '\n' ;
    vec.reserve(2000) ;
    std::cout << vec.size() << ' ' << vec.capacity() << '\n' ;
}


http://coliru.stacked-crooked.com/a/64fcc523d8d2413a
@JLBorges
if the current size and capacity (space) of the vector is five, and we call reserve(1000)
We won't call reserve(1000), that way it'd be arbitrarily; we only call it with a size doubled, if we need. Please have a look at the push_back function implementation.

Thank you very much for the code. As part of your programming habit/style, it's very elaborated and thorough, but too advanced for me. I can't take that high jump. Now I need to step forward gradually. Did you look at the questions I asked in the prior post, please?

On line 176, when I use:
myVec<int> vi4 = vi3 ;
instead of
myVec<int> vi4{ vi3 };
I get two errors:
initializing': cannot convert from 'myVec<int>' to 'myVec<int>
class "myVec<int>" has no suitable copy constructor

why?

If we add delete[] p (since it's not needed anymore, say, on the line 95), it won't be innocent because both p and elem point to the same address and if we delete p the memory will be freed for both elem and p, making elem useless (a dangling pointer), right?
Last edited on
re L176 error. Why are you declaring the copy constructor as explicit?

Also, don't use int for an index type. Use size_t (which is unsigned).

 
for (size_t i; i < newsize; ++i) elem[i] = 0;


No. i isn't initialsied.

1
2
for (size_t i = sz; i < newsize; ++i) 
    elem[i] = 0;


Last edited on
> Also, don't use int for an index type. Use size_t (which is unsigned).

The C++ language itself uses signed integer types for indexing; p[-3] is a perfectly valid construct in the language. There is a case for using std::size_t because it is consistent with the usage in the C++ standard library; no matter that many people (eg. Stroustrup) who were responsible for drawing up the standard now think that using unsigned integers for indexing was a design mistake. It is somewhat incongruous that the type used to index into a vector and specify its size is different from the one that is given by v.end() - v.begin()
@seeplus: I set the keyword explicit for constructors and assignment operators of a class which have only one argument. That's an advice I read from a book of Stroustrup if my recollection is accurate.

No. i isn't initialsied.
Is it the answer for the third question?
P is initialized inside the functions, e.g., line 89-90:

1
2
T* p = new T[arg.sz];
std::copy(arg.elem, arg.elem + arg.sz, p);
initializing': cannot convert from 'myVec<int>' to 'myVec<int>
class "myVec<int>" has no suitable copy constructor


That's just the same error. Try removing explicit from the copy constructor - its' usually only used for a single parameter 'ordinary' constructor to stop an unwanted implicit type conversion.
I got it, and thanks.
One last question I would like to ask JLBorges:
Your code does look amazing and that's my dream to be that advanced to code this way, and I try much to take advantages of it as much as possible, but it does require a hell of a lot of time for me to master it. Anyway, a simple question for this thread to finish is why have you used a struct not a class? Is it solely pertaining to your habit? I see it many a time on various resources!
Last edited on
A struct is a class and a class is a struct! The difference between them is that by default all members (variable/function et al) of a struct are public and for a class are private. So in JlBorges code, everything is public except for those marked private. In your class, everything by default is private except those marked public. So in your code, you have the public first so you need to mark these as public and then mark the private stuff as private. If you use a struct with the same ordering, you don't need the initial public as this is by default.

When/whether you use a class/struct is often down to in-house guidelines. However, an often used 'rule of thumb' as that if it's a 'plain simple' struct (with no private variables), then use a struct otherwise use a class. But in the absence of in-house guidelines, it's often a matter of personal preference.
Topic archived. No new replies allowed.
Pages: 12