Problems in creating a new vector (non-std vector)

Pages: 12
Hello everybody,

Here is the exercise number 15 of the book Programming Principles and Practice Using C++.
https://s2.postimg.org/wyjwt43ll/image.png
I don't understand what the exercise really wants us to do. Would you explain it a little?
Last edited on
Something like this, perhaps:

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 < typename T > struct pvector // like a vector of pointers
{
    pvector() = default ;

    // its destructor deletes each object
    ~pvector() { for( auto ptr : vec ) delete ptr ; }

    // for brevity, disable copy and move
    pvector( const pvector& ) = delete ;
    pvector( pvector&& ) = delete ;
    pvector& operator= ( const pvector& ) = delete ;
    pvector& operator= ( pvector&& ) = delete ;

    auto size() const { return vec.size() ; }
    // etc.
    
    // add pointers to T 
    void push_back( T* ptr ) { vec.push_back(ptr) ; }
    void pop_back() { delete vec.back() ; vec.pop_back() ; } // delete, and then pop
    // etc.

    // note: a user who calls delete on the returned pointer is
    // responsible for setting it (via the returned reference) to null
    T*& operator[] ( std::size_t pos ) { return vec[pos] ; }
    const T*& operator[] ( std::size_t pos ) const { return vec[pos] ; }
    // etc

    // it contains pointers to objects
    // note: private inheritance would have been handy for this,
    //       but I suppose that has not been covered as yet.
    private: std::vector<T*> vec ; // vector of pointers
};
I toyed with the code and I think the struct is vector called pvector that puts pointers to the std vector. That is, we have a vector called pvector that its elements/items are pointers of a type. And we (can) use pvector like this:

1
2
3
4
pvector<int> vi;
int i =5;
int* pi =&i;
vi.puch_back(pi);


right?

A couple of questions: (a brief answer for any of them would be sufficient and appreciated)
1- Why do C++ programmers nowadays tend to use structs instead of classes while structs are more of C and classes of C++?

2- What is the difference if we use pvector() { } rather than pvector() = default ; or even don't write this default constructor? Isn't using default that way of modern C++, say, C++11 or 14?

3- About these functions and if we likely want to implement them.
1
2
3
4
pvector( const pvector& ) = delete ;
    pvector( pvector&& ) = delete ;
    pvector& operator= ( const pvector& ) = delete ;
    pvector& operator= ( pvector&& ) = delete ;;


3-a) The first one should be the copy constructor. And the second and fourth should be move assignments. Well, I'm at the last page of chapter 20 of that book and I haven't seen a move assignment/operator or like that so far in the book. The book is the first edition. It must be either of C++11/14 or an expert level book. Right?

3-b)The third is a copy assignment. What is the difference between if we even don't write these four and the way we wrote and used delete for them? And what if we use empty curly brackets instead of that delete for them?

4- auto size() const { return vec.size() ; }, size is always an int I suppose, so what is the need for using auto in place of int there?

5-a)
1
2
 void pop_back() { delete vec.back() ; vec.pop_back() ; } // delete, and then pop
    // etc 

Why not just vec.pop_back();? Doesn't it do the job completely?
5-b) Wouldn't one of these statements suffice and would we need both of them?

6-a) What is the meaning of std::size_t pos, please? Is std::size_t an int or what? If an int, why not using int or even auto as above?

6-b) In T*& operator[] ( std::size_t pos ) { return vec[pos] ; }, the return type is a reference to a pointer. For example in our use, a reference to pi which is still a pointer. So we don't access the data, i, using that operator. Right? And if we want to access data, we should presumably employ this:
cout << *vi[0] ; , right again?

7- Why should we need this: const T*& operator[] ( std::size_t pos ) const { return vec[pos] ; }, a const one? Isn't the former enough?




1- Why do C++ programmers nowadays tend to use structs instead of classes while structs are more of C and classes of C++?

Normally many C++ programmers will use a struct when there is only data. But remember the only real difference between a class and a struct is the default access modes, a class defaults to private and a struct defaults to public. And many C++ programmers always specify the access mode and don't rely on the default access mode.

2- What is the difference if we use pvector() { } rather than pvector() = default ; or even don't write this default constructor? Isn't using default that way of modern C++, say, C++11 or 14?

Well the first two constructors are usually the same but yes using default is the more modern way. If your class has no other constructor then you really don't need either of the two shown "default" constructors, just use the "default" constructor provided by the compiler. However if you have another constructor defined the compiler will no longer create the no argument ("default") constructor and many times this constructor is required so you will need to create this "default" constructor.


3-a) The first one should be the copy constructor. And the second and fourth should be move assignments. Well, I'm at the last page of chapter 20 of that book and I haven't seen a move assignment/operator or like that so far in the book. The book is the first edition. It must be either of C++11/14 or an expert level book. Right?

Possibly.
3-b)The third is a copy assignment. What is the difference between if we even don't write these four and the way we wrote and used delete for them? And what if we use empty curly brackets instead of that delete for them?

When you use the "= delete" you prevent the compiler from using the "default" compiler generated operations. This means that you can't copy, assign, or move the class to another instance.
5-

Without seeing how that class is defined, I can't answer this question.

4- auto size() const { return vec.size() ; }, size is always an int I suppose, so what is the need for using auto in place of int there?

Actually size() is probably an implementation defined unsigned type, usually either an unsigned int or unsigned long. Using the auto lets the compiler figure out what type of variable to use for the return value at compile time. Doing this means that if you change the type of variable vec.size() returns the compiler will automatically adjust the type of variable.

6-a) What is the meaning of std::size_t pos, please? Is std::size_t an int or what? If an int, why not using int or even auto as above?

See 4 above. You need to be specific about the parameter type, the compiler can rarely determine the "correct" type so you must provide the type in that constant.

7- Why should we need this: const T*& operator[] ( std::size_t pos ) const { return vec[pos] ; }, a const one? Isn't the former enough?

No, You need to insure const correctness a const int is not the same as a non-const int.


Last edited on
> we have a vector called pvector that its elements/items are pointers of a type

Yes.


> we (can) use pvector like this:
1
2
3
4
> pvector<int> vi;
> int i =5;
> int* pi =&i;
> vi.puch_back(pi);

No - "its destructor deletes each object." So a pointer that he hand over to pvector must be either a pointer returned by the new expression new T or a nullptr.

This is fine: vi.push_back( new int(5) ) ; // destructor of pvector would call delete


> Why do C++ programmers nowadays tend to use structs instead of classes

I do not think that all C++ programmers nowadays tend to use structs instead of classes;
I do, but I suspect that I'm in the minority.
See: http://www.cplusplus.com/forum/beginner/220207/#msg1013861


> What is the difference if we use pvector() { } rather than pvector() = default ;

There is no difference.



> About these functions and if we likely want to implement them.
1
2
3
4
> pvector( const pvector& ) = delete ;
> pvector( pvector&& ) = delete ;
> pvector& operator= ( const pvector& ) = delete ;
> pvector& operator= ( pvector&& ) = delete ;


> The first one should be the copy constructor - Yes.

> And the second and fourth should be move assignments - the second one is the move constructor and the fourth one is the move assignment operator.

> I haven't seen a move assignment/operator or like that so far in the book.
> The book is the first edition. It must be either of C++11/14 or an expert level book. Right?

I do not have the first edition of the book. The second edition briefly mentions about move semantics
in 18.3 Copying (18.3.4 Moving) and 18.4 Essential operations.

Yes, implementing move it is not a topic for beginners to dabble in: most of the classes written by beginners would (should) be following the rule of zero. http://en.cppreference.com/w/cpp/language/rule_of_three


The third one is the copy assignment operator - Yes.

> What is the difference between if we even don't write these four

The implementation (the compiler) would provide implicitly declared operations for copy and move operations; in this case that would be a disaster because both he original and the copy, in their destructors would try to delete the same objects.


> And what if we use empty curly brackets instead of that delete for them?

The contents would not be copied or moved; for example, the copy constructor would not make a copy of the object. This would be semantically wrong.


> auto size() const { return vec.size() ; }, size is always an int I suppose,
> so what is the need for using auto in place of int there?

The size is always an unsigned integer type; the underlying vectors size_type. In our case, (and in almost every case), this would be std::size_t http://en.cppreference.com/w/cpp/types/size_t


> Why not just vec.pop_back();? Doesn't it do the job completely?

It does the job of removing the element. However, because of the specification: "its destructor deletes each object.", the vector owns the object that is pointed to; it is responsible for deleting the object before throwing the pointer away.


> Wouldn't one of these statements suffice and would we need both of them?

The first one deletes the object that is pointed to; the second one removes the pointer to the object from the vector. Both are required.


> And if we want to access data, we should presumably employ this: cout << *vi[0] ;

Yes.
Ideally, after verifying that it is not a null pointer. This check for null is something we need to do always, before we try to access an object via a pointer given to us by some other component.


> Why should we need this: const T*& operator[] ( std::size_t pos ) const { return vec[pos] ; },
> a const one? Isn't the former enough?

See 'What’s the deal with “const-overloading”?' https://isocpp.org/wiki/faq/const-correctness
Thanks.
pvector() { } rather than pvector() = default ;
There is no difference.

What if we even don't declare these? Aren't these exactly those which the compiler implicitly creates when there isn't any? If yes, so we can drop them, not?

most of the classes written by beginners would (should) be following the rule of zero

What rule of zero says is: Classes that have custom destructors, copy/move constructors or copy/move assignment operators, should do a unique work and be encapsulated.

By custom it means the ones we ourselves create not the default ones that are created by the compiler in the absence of our ones, yes?
And why does it have used "encapsulated"? Isn't each and every class encapsulated initially in essence?


... in this case that would be a disaster because both he original and the copy, in their destructors would try to delete the same objects.

Does it because implicitly created constructors, destructors, copy/move constructors, copy/move assignment operators all do a shallow copy and not deep copy?

http://en.cppreference.com/w/cpp/types/size_t

Can we say we should replace using unsigned int with size_t in all cases or only loops and indexing?

Why using std:: for types but not a more convenient remedy: declaring using namespace std; at the beginning of the code?

template < typename T >

What is the difference if we write class instead of typename in templates? It seems to be of a modern case to me.

(Sorry for the delay)

Last edited on
> What if we even don't declare these? Aren't these exactly those which the compiler
> implicitly creates when there isn't any? If yes, so we can drop them, not?

If we do not declare a default constructor, the class will not be DefaultConstructible The compiler will provide an implicitly declared default constructor if and only if there are no user-declared constructors at all.


> By custom it means the ones we ourselves create not the default ones that are created by the compiler

Yes. User defined destructors, copy/move constructors and copy/move assignment operators.


> And why does it have used "encapsulated"?

In this context, it means that correctly copying or moving an object, assigning one object to another, and performing proper clean up when the life-time of the object is over is the responsibility of the writer of the class. To the user of the class, these are implementation details that the user does not have to worry about.


> Does it because implicitly created constructors, destructors, copy/move constructors,
> copy/move assignment operators all do a shallow copy and not deep copy?

Semantically, implicitly defined copy/move constructors perform a member-wise copy/move; and implicitly defined assignment operators perform a member-wise assignment.

That means that for a member object of type std::string, the appropriate operation defined by std::string would be used; this would be a 'deep' copy.

For a member object which is a raw pointer, the pointer would be copied. The object pointed to would not be copied; this would be a 'shallow' copy.

Moral of the story: use std::string, std::vector etc. or a smart pointer instead of raw 'owning' pointers.


> Can we say we should replace using unsigned int with size_t in all cases or only loops and indexing?

Ideally, use std::size_t for specifying the size of an array/object or an index into an array. We can also use std::size_t for this purpose with standard library containers (when the allocator is the default allocator). In other places, use types like int or [tt[unsigned int[tt]

Note: use [tt]std::ptrdiff_t[/tt] to represent the result of subtracting one ppointer from another or a possibly negative offset. http://en.cppreference.com/w/cpp/types/ptrdiff_t


> Why using std:: for types but not a more convenient remedy:
> declaring using namespace std; at the beginning of the code?

Do not use using declarations and directives in a header file or in any file before a #include directive.

Other than that, there are two valid points of view:

One which says that using declarations and directives are perfectly acceptable in implementation files, especially if it is local to a scope; if you perceive that it aids readability, use it.

In short: You can and should use namespace using declarations and directives liberally in your implementation files after #include directives and feel good about it. Despite repeated assertions to the contrary, namespace using declarations and directives are not evil and they do not defeat the purpose of namespaces. Rather, they are what make namespaces usable.
...
But using declarations and directives are for your coding convenience, and you shouldn't use them in a way that affects someone else's code. In particular, don't write them anywhere they could be followed by someone else's code: Specifically, don't write them in header files (which are meant to be included in an unbounded number of implementation files, and you shouldn't mess with the meaning of that other code) or before an #include (you really don't want to mess with the meaning of code in someone else's header).

- Sutter and Alexandrescu in 'C++ Coding Standards: 101 Rules, Guidelines, and Best Practices'



SF.6: Use using namespace directives ... for foundation libraries (such as std) ...

using namespace can lead to name clashes, so it should be used sparingly. However, it is not always possible to qualify every name from a namespace in user code (e.g., during transition) and sometimes a namespace is so fundamental and prevalent in a code base, that consistent qualification would be verbose and distracting.

CoreGuideLines https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Rs-using


The other, which says that using explicit namespace prefixes, even in implementation files, makes the code clearer, and more portable.

In implementation files (e.g. .cpp files), the rule is more of a stylistic rule, but is still important. Basically, using explicit namespace prefixes makes the code clearer, because it is immediately obvious what facilities are being used and where they are coming from. And more portable, because namespace clashes cannot occur between LLVM code and other namespaces. The portability rule is important because different standard library implementations expose different symbols (potentially ones they shouldn't), and future revisions to the C++ standard will add more symbols to the std namespace. As such, we never use 'using namespace std;' in LLVM.
- LLVM Coding Standardsre portable.


I belong to the second school (using explicit namespace prefixes, even in implementation files, makes the code clearer, and more portable); so that is what I tend to do most of the time.

Think about it, and then follow what appeals to you more.
Thank you JLBorges and jlb.

1- I also read the beforehand post and should say that I still don't figure out why we should use auto for size()! As it was said, it's always an unsigned int, so why not using it or size_t instead? I mean, what's the benefit of using that over these two?

2- Does the pop_back(); function in the standard vector remove that node from vector and also set it to null?

3- T*& operator[] (std::size_t pos) { return vec[pos]; }, Why do we need *&? The T* is a pointer and pointers can be used like references, so why do we still need & there, please?
Last edited on
> why we should use auto for size()

We can use std::size_t size(), or typename std::vector<T*>::size_type, instead of auto.
Using auto is slightly more flexible: the compiler would always figure out what the correct type is (even if we change the type of the container to, say, one with a user defined allocator with a different size_type); and here, there is no extra burden on the user of the class who would know that semantically size() returns the number of elements in pvector.


> Does the pop_back(); function in the standard vector remove that node from vector and also set it to null?

It removes the last element from the vector.
The question of setting it to something does not arise, because it does not exist after it has been removed.


> T*& operator[] (std::size_t pos) { return vec[pos]; }, Why do we need *&?

A reference to the pointer would allow the user to modify the value of the pointer which is in pvector. It is a non-trivial design decision whether allowing this in this class (external modification of the value of an owning pointer) is a good idea or not.
The question of setting it to something does not arise, because it does not exist after it has been removed.

I meant this note of you.
note: a user who calls delete on the returned pointer is responsible for setting it (via the returned reference) to null.

We delete the element then remove it from the vector, should we still get back and reach (?) to set it to null?


A reference to the pointer would allow the user to modify the value of the pointer which is in pvector.

Yes, I see, but I think references are not needed for pointers, as such, using and not using that & in the code showed no difference in output.

> We delete the element then remove it from the vector.

We delete the object pointed to, and then set the pointer in the vector to nullptr.


> I think references are not needed for pointers

They are needed if we want to modify the value of the original pointer.
Without references, we would be working with copies of pointers and not the originals.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
    pvector<int> seq ;

    seq.push_back( new int(23) ) ;
    seq.push_back( new int(12) ) ;
    seq.push_back( new int(64) ) ;

    int*& ptr = seq[1] ; // get a reference to the pointer
    std::cout << *ptr << '\n' ; // 12

    // a user who calls delete on the returned pointer is
    // responsible for setting it (via the returned reference) to null
    delete ptr ;
    ptr = nullptr ; // the vector will delete the pointer;
                    // this is fine: delete nullptr is a no op

    // the above wouldn't work unless seq[1] returned a reference to the pointer
    // change line 45 to:
    // int* ptr = seq[1] ; // get a copy of the pointer
    // and this would engender undefined behaviour (double delete)
}
I don't understand why we need to delete that ptr pointer.

And if it's a reference to an element of pvector , as it is, (or not, when we don't use reference), I suppose, we can't simply delete or set it to nullptr. Because this way we have done this to that element of the vector too. And therefore making an error (that I face on my compiler).

The vector is a (doubly) linked list and we should get the pointers of the previous and next elements sorted out after removing an element in between.
I think the vector has its own functions specific for removing an element from that. Do you disagree?
Last edited on
> I don't understand why we need to delete that ptr pointer.

We don't need to: the point was to illustrate the case 'if we need to'


> I suppose, we can't simply delete or set it to nullptr.

We can delete it and set it to nullptr in the vector. Then, when the vector, at the end, deletes that pointer, nothing happens because delete of a null pointer does not do anything.


> The vector is a (doubly) linked list

std::vector<> is a dynamically resizeable array.
http://en.cppreference.com/w/cpp/container/vector


> I think the vector has its own functions specific for removing an element from that.

Yes. Though we do not need to do it for this exercise; setting the pointer in the vector to null would suffice.



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
#include <vector>
#include <iostream>
#include <string>

template < typename T > struct pvector // like a vector of pointers
{
    pvector() = default ;

    // its destructor deletes each object
    ~pvector() { for( auto ptr : vec ) delete ptr ; }

    // for brevity, disable copy and move
    pvector( const pvector& ) = delete ;
    pvector( pvector&& ) = delete ;
    pvector& operator= ( const pvector& ) = delete ;
    pvector& operator= ( pvector&& ) = delete ;

    auto size() const { return vec.size() ; }
    // etc.

    // add pointers to T
    void push_back( T* ptr ) { vec.push_back(ptr) ; }
    void pop_back() { delete vec.back() ; vec.pop_back() ; } // delete, and then pop
    // etc.

    // note: a user who calls delete on the returned pointer is
    // responsible for setting it (via the returned reference) to null
    T*& operator[] ( std::size_t pos ) { return vec[pos] ; }
    const T*& operator[] ( std::size_t pos ) const { return vec[pos] ; }
    // etc

    void debug_dump() const // print out the value of the pointers in the vector
    {
        std::cout << "pointers in the vector: " ;
        for( auto p : vec )
        {
            if(p) std::cout << p << ' ' ;
            else std::cout << " nullptr " ;
        }
        std::cout << "( in all " << size() << " pointers)\n" ;
    }

    // it contains pointers to objects
    // note: private inheritance would have been handy for this,
    //       but I suppose that has not been covered as yet.
    private: std::vector<T*> vec ; // vector of pointers
};

int main()
{
    struct A
    {
        ~A()
        {
            std::cout << "destroy object with name '" << name
                      << "' at address " << this << "'\n" ;
        };
        std::string name ;
    };

    {
        pvector<A> seq ;

        seq.push_back( new A { "zero" } ) ;
        seq.push_back( new A { "one" } ) ;
        seq.push_back( new A { "two" } ) ;
        seq.push_back( new A { "three" } ) ;
        seq.push_back( new A { "four" } ) ;
        seq.push_back( new A { "five" } ) ;

        seq.debug_dump() ;

        A*& ptr = seq[3] ; // get the pointer at position 3
        if(ptr)
        {
            std::cout << "\nptr " << ptr << " points to object A with name '" << ptr->name << "'\n"
                      << "delete the object => " ;
            delete ptr ;
            seq.debug_dump() ;

            std::cout << "\nset the dangling pointer to null\n" ;
            ptr = nullptr ;
            seq.debug_dump() ;
        }

        std::cout << "\nabout to destroy pvector<A> 'seq'"
                     "\n\tits destructor would delete the pointers it contains\n" ;
    }
}

pointers in the vector: 0xd69c20 0xd69c70 0xd69cc0 0xd69d20 0xd69d50 0xd69cf0 ( in all 6 pointers)

ptr 0xd69d20 points to object A with name 'three'
delete the object => destroy object with name 'three' at address 0xd69d20'
pointers in the vector: 0xd69c20 0xd69c70 0xd69cc0 0xd69d20 0xd69d50 0xd69cf0 ( in all 6 pointers)

set the dangling pointer to null
pointers in the vector: 0xd69c20 0xd69c70 0xd69cc0  nullptr 0xd69d50 0xd69cf0 ( in all 6 pointers)

about to destroy pvector<A> 'seq'
	its destructor would delete the pointers it contains
destroy object with name 'zero' at address 0xd69c20'
destroy object with name 'one' at address 0xd69c70'
destroy object with name 'two' at address 0xd69cc0'
destroy object with name 'four' at address 0xd69d50'
destroy object with name 'five' at address 0xd69cf0'

http://coliru.stacked-crooked.com/a/c1ce60c37684c133
Please let me first clear the issues for myself. Please take a look at this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <std_lib_facilities_4.h>
using namespace std;

int main()
{
	vector<int*> vp;
	for (size_t i = 0; i < 5; i++)
		vp.push_back(new int(i));

	int* pi = vp[2];
	cout << *pi << endl;
	delete pi;

	int k = 5;
	int* pk = &k;
	cout << *pk << endl;
	delete pk;

	system("pause");
	return 0;
}


A couple of questions:
1- How to reach the address of the each box of the vector, that is, the vector vp has 5 boxes, how to reach the address of each one, for example, to see if they are adjacent or not?

2- In line 10 we declare a pointer pi. We initialise it by an address (vp[2]), show its value (*pi), and then delete it. No error comes up. In line 15 we declare another pointer (pk), initialise it by an address (&k), show its value (*pk), and then delete it. An exception error comes up! Why do we face an error with deleting pk, but not with pi?



Last edited on
> How to reach the address of the each box of the vector,
> that is, the vector vp has 5 boxes, how to reach the address of each one

Just iterate through the sequence and take the address of each element. Fr instance:

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
#include <iostream>
#include <vector>
#include <cstdint>

// dump the addresses of elements and offsets between successive elements
// in a sequence container or array
template < typename CNTR > void dump_addresses( CNTR& cntr )
{
    std::cout << "            raw addresses: " ;
    for( const auto& v : cntr ) std::cout << std::addressof(v) << ' ' ;
    std::cout << '\n' ;

    std::cout << "address values in decimal: " ;
    for( const auto& v : cntr ) std::cout << reinterpret_cast< std::uintptr_t>( std::addressof(v) ) << ' ' ;
    std::cout << '\n' ;

    std::cout << "       offsets in decimal: " ;
    auto last = reinterpret_cast< std::uintptr_t>( std::addressof( *std::begin(cntr) ) ) ;
    unsigned long long i = 0 ;
    for( const auto& v : cntr )
    {
        if( i++ == 0 ) continue ;
        const auto curr = reinterpret_cast< std::uintptr_t>( std::addressof(v) ) ;
        std::cout << curr - last << ' ' ;
        last = curr ;
    }
    std::cout << '\n' ;
}

int main()
{
    std::cout << "sizeof(int): " << sizeof(int) << '\n' ;
    std::vector<int> vec_int { 12, 89, 34, 56, 78, 21, 11, 92, 77, 52, 28 } ;
    dump_addresses(vec_int) ;

    struct A { double d = 0 ; char c[77] {}; };
    std::cout << "\n\nsizeof(A): " << sizeof(A) << '\n' ;
    std::vector<A> vec_a(16) ;
    dump_addresses(vec_a) ;

}

http://coliru.stacked-crooked.com/a/7ba09fdedd38d32b


> Why do we face an error with deleting pk, but not with pi?

The pointer pi points to an object that has dynamic storage duration;
the object was created by a new expression (new int).

The pointer pk points to an object that has does not have dynamic storage duration;
the object was not created by a new expression.

We an use a delete expression only with a pointer that points to an object created using a compatible new expression (or with a null pointer).
Thanks. This code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <std_lib_facilities_4.h>
using namespace std;

int main()
{
	vector<int*> vp;
	for (size_t i = 0; i < 5; i++)
		vp.push_back(new int(i));

	int* pi = vp[2];
	delete pi;
	pi = nullptr;

	system("pause");
	return 0;
}


What does delete pi; do over pi, in reality?
And why does this action affect vp[2]? It seems that they are independent.
> What does delete pi; do over pi, in reality?
> And why does this action affect vp[2]? It seems that they are independent.

I think you need to re-read chapter 17. In particular, 17.4 'Freestore and pointers'
> What does delete pi; do over pi, in reality?
It just disconnects the connection between the pointer and the object pointed to by it. So, the pointer is dangling now and points to some other object in memory randomly, so it might contain a (trash) value. We then make it a nullptr so that if we suddenly indeliberately use it, its trash value doesn't cause a problem for us. Right?

> And why does this action affect vp[2]? It seems that they are independent.
I'm not really sure why deleting pi should delete p[2] too.

A side question: Do you develop Graphical C++ applications? If so, what library/framework or IDE do you use for that please?
> What does delete pi; do over pi, in reality?

It does nothing to the pointer pi itself; but if pi is not a null pointer, it ends the life time of the object that pi points to. It destroys the object (invokes the destructor if the destructor is not trivial) and then invokes the deallocation function to release the storage that was occupied by the object. After this, the pointer is a dangling pointer: it contains the old address, but there is no object at that address.

> We then make it a nullptr so that if we suddenly indeliberately use it,
> its trash value doesn't cause a problem for us. Right?

If we inadvertently try to access an object after its life time is over through the original pointer or try to access the object through a nullptr, it would engender undefined behaviour.

The only benefit of adhering to the palooka recommendation: 'always set the pointer to null after delete' and setting pi to null is that it allows a check like if( pi != nullptr ) { ...; and that a second delete pi ; would do nothing - that too only on that particular pointer variable (not on copies that may have been made).

The non-palooka recommendation would be:
a. favour scoped objects over objects with dynamic storage duration
b. use either use a smart pointer to represent ownership or use facilities like std::vector<> which automate resource management.

In this particular case, we should make the pointer in the vector vp[2] a null pointer because the exercise specifies that for this vector, "its destructor deletes each object.". If we set vp[2] to null, the attempt to delete the object would do nothing; if we don't, the attempt to delete an already deleted object would engender undefined behaviour.


> And why does this action affect vp[2]?

After int* pi = vp[2];, pi is a copy of the pointer in the vector; both pi and vp[2] point to the same object; it is this object that is destroyed.


> Do you develop Graphical C++ applications?

No. Sometimes, the programs I develop have a graphical front-end; but those front-ends are separate programs written by someone else, often in languages other than C++.
I got it.
If we use:
1
2
3
int*& pi = vp[2];
delete pi;
pi = nullptr;

this deletes and puts nullptr to both the pi and the element returned by vp[2] and that's because the std::vector has been implemented like T*& operator[]. This way, making it able to return by reference.
Thanks.

And about C++, it sounds that you are dealing with only console applications in the area of C++. Would you tell me in what fields do you use it? I mean, where are console applications (those codes resulting in a black and white screen) useful in the world of jobs? In other words, is it possible a company employs a C++ console programmer?
Last edited on
Pages: 12