Proper use of this->pointer?

Below I have two functions that expand the length of a pointer to pointer array that I created in a class. Both functions are identical except the use of the this->pointer.

In this case is the this->pointer just for the programmer to distinguish a variable of the calling object or does it have more meaning?




void PoPArray::expand(){

m_length *= 2; //Increase member variable m_length by double

int **temp = new int*[m_length]; // Create temp pointer to pointer array

for(int i = 0; i < m_elements; i++) // Tranfer data to temp array
{
temp[i] = new int(*ptr[i]);

}
for(int i = 0; i < m_elements; i++) //Free memory pointed to by old array
{
delete this->ptr[i];
}
delete[] this->ptr;
this->ptr = temp; // reinstate old array with new length

}





void PoPArray::expand(){

m_length *= 2;

int **temp = new int*[m_length];

for(int i = 0; i < m_elements; i++)
{
temp[i] = new int(*ptr[i]);

}



for(int i = 0; i < m_elements; i++)
{
delete ptr[i];
}
delete[] ptr;
ptr = temp;

}

> In this case is the this->pointer just for the programmer to distinguish a variable of the calling object

Yes.

In this case, it serves no purpose other than documenting intent.

In other cases, it more than just documentation; for instance when
the name of the member has been hidden by a name introduced into the local scope
or (templates) when we want to specify that the name is a dependant name.

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
template < typename T > struct A
{
    T value = 6 ;

    void foo( T value ) // the parameter 'value' hides member 'A::value'
    {
        value = value ; // trivial self-assignment; does not assign to A::value

        A::value = value ; // fine
        this->value = value ; // also fine
        this->A::value = value ; // also fine, though over-elaborate
    }
};

template < typename T > struct B : A<T>
{
    void bar( T x )
    {
        // value = x ; // error: non-dependant name 'value' not found
        
        this->value = x ; // fine; tell the compiler that 'value' is a dependant name
        B::value = x ; // also fine; tell the compiler that 'value' is a dependant name
        B<T>::value = x ; // also fine; tell the compiler that 'value' is a dependant name
        A<T>::value = x ; // also fine; tell the compiler that 'value' is a dependant name
        this->B::value = x ; // also fine; tell the compiler that 'value' is a dependant name
    }
};
OP: you can transfer ownership of the objects already created to the pointers in the expanded pointer array without (a) creating additional objects and (b) deleting the objects already created. Below an example for a custom type:
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>
#include <string>
#include <utility>
constexpr auto SIZE = 2;

struct Movie {std::string m_name; Movie(const std::string& name) : m_name(name){} ~Movie(){std::cout << "Bye " << m_name << "\n";}};

Movie** expand(Movie** original)
{
    Movie** originalDoubled = new Movie*[2 * SIZE];
   for (size_t i = 0; i < SIZE; ++i)
    {
        originalDoubled[i] = original[i];//give ownership of original's objects to originalDoubled;
        original[i] = nullptr;//release original's pointers from ownership
    }
    for (size_t i = SIZE; i < 2 * SIZE; ++i)
    {
        std::cout << "Enter movie name \n";
        std::string name{};
        getline(std::cin, name);
        *(originalDoubled + i) = new Movie(name);//add new objects to originalDoubled
    }
    delete [] original;//delete the original array
    return originalDoubled;
}
void displayName(Movie** ptr_Array, const size_t n)
{
    for (size_t i = 0; i < n; ++i)
    {
        std::cout << (*(*(ptr_Array+i))).m_name << "\n";
        //inner dereference gives array element which is a pointer
        //outer dereference dereferences this array element to the underlying Movie object
    }
}
int main()
{
    Movie** ptr_Array = new Movie*[SIZE];
    for (size_t i = 0; i < SIZE; ++i)
    {
        std::cout << "Enter movie name \n";
        std::string name{};
        getline(std::cin, name);
       *(ptr_Array + i) = new Movie(name);//overload ctor as required
    }
    std::cout << "Original movie list \n";
    displayName(ptr_Array, SIZE);

    Movie** originalDoubled {std::move(expand(ptr_Array))};
    http://en.cppreference.com/w/cpp/utility/move

    std::cout << "Expanded movie list \n";
    displayName(originalDoubled, 2 * SIZE);
    for (size_t i = 0; i < 2 * SIZE; ++i)
    {
        delete originalDoubled[i];//deletes the Movie objects
    }
    delete [] originalDoubled;//deletes the dynamically allocated array
}
Last edited on
And using std::vector with std::unique_ptr help (a) not needing a new container (though the vector may reallocate which can be avoided by vector.reserve()) and (b) perhaps the greater advantage of not needing to explicitly delete pointers along the way:
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
#include <iostream>
#include <string>
#include <vector>
#include <memory>
constexpr auto SIZE = 2;

struct Movie {std::string m_name; Movie(const std::string& name) : m_name(name){} ~Movie(){std::cout << "Bye " << m_name << "\n";}};
using MoviePtrs = std::vector<std::unique_ptr<Movie>>;

MoviePtrs& expand(MoviePtrs& original)
{
   for (size_t i = 0; i < SIZE; ++i)
    {
        std::cout << "Enter movie name \n";
        std::string name{};
        getline(std::cin, name);
        original.push_back(std::make_unique<Movie>(name));
    }
    return original;
}
void displayName(const MoviePtrs& original)
{
    static int counter{};
    if(counter == 0)std::cout << "Original movie list \n"; else std::cout << "Expanded movie list \n";
    for (const auto& elem : original) { std::cout << elem -> m_name << "\n"; }
    ++counter;
}
int main()
{
    MoviePtrs original{};

    displayName(expand(original));
    displayName (expand(original));
}
A more subtle reason to prefer unique_ptr is exception-safety. If the new expression inside your for-loop throws, you will leak resources.

This would not be the case if you use unique_ptr (or a vector of them) since the vector has automatic storage duration and will be destroyed in the process of stack unwinding, correctly.
Strongly favour value semantics over any kind (raw or smart) of pointer semantics.

For non-polymorphic types (like Movie above), when the container has sole ownership of the objects, use std::vector<Movie>, not std::vector< std::unique_ptr<Movie> >


If we want to have a containers of pointers to Movie, it is because we want the container to hold references to external objects (objects not exclusively owned by the container).

If the container does not own the objects:
hold non-owning raw pointers eg. std::vector<Movie*>
(or wrapped references, or weak pointers if shared pointers are involved).

If the container must have shared ownership of the objects:
a suitable smart pointer eg. std::vector< std::shared_ptr<Movie> >
A more subtle reason to prefer unique_ptr is exception-safety. If the new expression inside your for-loop throws, you will leak resources.

this would require std::unique_ptr && std::make_unique, direct use of new with std::unique_ptr would still leave open potential resource leak (cf. Item 21, Effective Modern CPP)
Strongly favour value semantics over any kind (raw or smart) of pointer semantics.

yes, agreed. OP was already on the path to array of (raw) pointers, this point deserves a mention to him/her at the outset
direct use of new with std::unique_ptr would still leave open potential resource leak

Why's this?

std::unique_ptr<Movie> blah{new Movie("Forrest Gump")};
Last edited on
The pre-C++17 edge case with directly constructed unique pointers was this:

With foo( std::unique_ptr<X>( new X ), bar() ); the order of evaluation might be

1
2
3
1. auto temp1 = new X ;
2. auto temp2 = bar() ; // this may throw
3. ... 


With foo( std::make_unique<X>(), bar() );
std::make_unique<> is a function, and the evaluations of two functions will never interleave.
Topic archived. No new replies allowed.