When Should I Make Explicit Use of the 'This' Pointer

When should I make explicit use of the `this` pointer?

Usually, you do not have to, this-> is implied.

Sometimes, there is a name ambiguity, where it can be used to disambiguate class members and local variables. However, here is a completely different case where this-> is explicitly required.

Consider the following code:

template<class T>
struct A {
T i;
};

template<class T>
struct B : A<T> {
T foo() {
return this->i; //standard accepted by all compilers
//return i; //clang and gcc will fail
//clang 13.1.6: use of undeclared identifier 'i'
//gcc 11.3.0: 'i' was not declared in this scope
//Microsoft C++ Compiler 2019 will accept it
}

};

int main() {
B<int> b;
b.foo();
}

If you omit this->, some compilers do not know how to treat i. In order to tell it that i is indeed a member of A<T>, for any T, the this-> prefix is required.

Note: it is possible to still omit this-> prefix by using:

template<class T>
struct B : A<T> {
int foo() {
return A<T>::i; // explicitly refer to a variable in the base class
//where 'i' is now known to exist
}

};

Is it okay to use the this pointer?

Consistency consistency consistency.

I conisder the this-> prefix a valid coding style if you use it throughout your entire project everywhere a member is accessed.

I prefer using a signifying prefix for members, e.g. m_. I feel it is less cutter and less tag soup than the explicit this->:

(alpha-this->gamma > this->alpha-gamma)

vs.

(alpha-m_gamma > m_alpha-gamma)

(The dotNetties have labeled m_ outdated - I use it on small C# projects out of spite. but anyway, any other distinct prefix would do, too.)

I've seen it used often to help intellisense get in gear, or to specifically filter members - which is ok, though leaving it in for that reason is questionable, especially if not used consistently.

Is it a good practice to use the this pointer in class methods?

Slower no, but it helps if there is a variable in scope which is the same as the closing members, you can use this to disambiguate between the two. You can also assign to this which functionality can also be overridden.

When to use this pointer in member function

It is never required inside a normal function, unless there is a parameter with the same name as a member. In a constructor you can use an initalizer list to prevent ambiguity. The use of a this pointer might be required when you use templates.

C++ proper usage, this pointer

In most cases, specifically dereferencing the this pointer to access a non-static data-member of the class instance is unnecessary, but it can help with naming confusion, especially when the data-members of the class are defined in a separate header file from the code-module. However, it is necessary to use the this pointer if you are accessing a non-static data-member that is a member of a base-class that is a templated class. In other words, in a situation like:

template<typename T>
class base_class
{
protected:
int a;
};

template<typename T>
class derived_class : public base_class<T>
{
void function()
{
a = 5; //this won't work
this->a = 5; //this will work
}
};

you'll note that you must use the this pointer in order to properly resolve the inherited non-static data-member from the template base-class. This is due to the fact that base_class<T>::a is a dependent name, in this case dependent on the template parameter T, but when used without the this pointer, it is treated as a non-dependent name, and is therefore not looked up in the dependent base-class namespace. Thus without the specific dereference of the this pointer, you'll end up with a compiler error like "a was not declared in this scope", or something similar.

Why can't I pass the this pointer explicitly to a member function?

Lets add a static member to the class as:

 class foo{
public:
void bar() { cout<<"hey there"<<endl; }
static void bar(foo*) { cout<<"STATIC MEMBER"<<endl; }
};

Now if you write this:

 foo::bar(&obj); //static or non-static?

Which function should be called? In such situation, how would you call both of them? What would be the syntax? If you allow one function to have this syntax, you've to abandon it (i.e syntax) for other function. The Standard decided to have foo::bar(&obj) syntax for static member function, while abandoning it for non-static member function.


Anyway, if you want to pass &obj as argument to the non-static member function, then you can use type-erasure facilitated by std::function as:

 void (foo::*pbar)() = &foo::bar; //non-static member function   #1

std::function<void(foo*)> bar(pbar);

bar(&obj); //same as obj.bar();

Likewise, you could call static member function as:

 void (*pbar)(foo*) = &foo::bar; //static member function            #2

std::function<void(foo*)> bar(pbar);

bar(&obj); //same as foo::bar(&obj);

Note that at lines #1 and #2, the types of the object pbar makes the compiler to choose the correct member function — in the first case, it takes the pointer to the non-static member-function while in the latter case, it takes the pointer to the static member function.

Hope that helps.

Why should I use a pointer rather than the object itself?

It's very unfortunate that you see dynamic allocation so often. That just shows how many bad C++ programmers there are.

In a sense, you have two questions bundled up into one. The first is when should we use dynamic allocation (using new)? The second is when should we use pointers?

The important take-home message is that you should always use the appropriate tool for the job. In almost all situations, there is something more appropriate and safer than performing manual dynamic allocation and/or using raw pointers.

Dynamic allocation

In your question, you've demonstrated two ways of creating an object. The main difference is the storage duration of the object. When doing Object myObject; within a block, the object is created with automatic storage duration, which means it will be destroyed automatically when it goes out of scope. When you do new Object(), the object has dynamic storage duration, which means it stays alive until you explicitly delete it. You should only use dynamic storage duration when you need it.
That is, you should always prefer creating objects with automatic storage duration when you can.

The main two situations in which you might require dynamic allocation:

  1. You need the object to outlive the current scope - that specific object at that specific memory location, not a copy of it. If you're okay with copying/moving the object (most of the time you should be), you should prefer an automatic object.
  2. You need to allocate a lot of memory, which may easily fill up the stack. It would be nice if we didn't have to concern ourselves with this (most of the time you shouldn't have to), as it's really outside the purview of C++, but unfortunately, we have to deal with the reality of the systems we're developing for.

When you do absolutely require dynamic allocation, you should encapsulate it in a smart pointer or some other type that performs RAII (like the standard containers). Smart pointers provide ownership semantics of dynamically allocated objects. Take a look at std::unique_ptr and std::shared_ptr, for example. If you use them appropriately, you can almost entirely avoid performing your own memory management (see the Rule of Zero).

Pointers

However, there are other more general uses for raw pointers beyond dynamic allocation, but most have alternatives that you should prefer. As before, always prefer the alternatives unless you really need pointers.

  1. You need reference semantics. Sometimes you want to pass an object using a pointer (regardless of how it was allocated) because you want the function to which you're passing it to have access that that specific object (not a copy of it). However, in most situations, you should prefer reference types to pointers, because this is specifically what they're designed for. Note this is not necessarily about extending the lifetime of the object beyond the current scope, as in situation 1 above. As before, if you're okay with passing a copy of the object, you don't need reference semantics.

  2. You need polymorphism. You can only call functions polymorphically (that is, according to the dynamic type of an object) through a pointer or reference to the object. If that's the behavior you need, then you need to use pointers or references. Again, references should be preferred.

  3. You want to represent that an object is optional by allowing a nullptr to be passed when the object is being omitted. If it's an argument, you should prefer to use default arguments or function overloads. Otherwise, you should preferably use a type that encapsulates this behavior, such as std::optional (introduced in C++17 - with earlier C++ standards, use boost::optional).

  4. You want to decouple compilation units to improve compilation time. The useful property of a pointer is that you only require a forward declaration of the pointed-to type (to actually use the object, you'll need a definition). This allows you to decouple parts of your compilation process, which may significantly improve compilation time. See the Pimpl idiom.

  5. You need to interface with a C library or a C-style library. At this point, you're forced to use raw pointers. The best thing you can do is make sure you only let your raw pointers loose at the last possible moment. You can get a raw pointer from a smart pointer, for example, by using its get member function. If a library performs some allocation for you which it expects you to deallocate via a handle, you can often wrap the handle up in a smart pointer with a custom deleter that will deallocate the object appropriately.

C++ explicit constructor that takes a pointer

The following code:

void f(Foo) {}

int main()
{
int* p;
f(p);
}
  • Fails to compile with explicit.

  • Happily compiles without it.

live example on godbolt.org

What is the advantage of having this/self pointer mandatory explicit?

In addition to member variables and method parameters you also have local variables. One of the most important things about the object is its internal state. Explicit member variable dereferencing makes it very clear where you are referencing that state and where you are modifying that state.

For instance, if you have code like:

someMethod(some, parameters) {
... a segment of code
foo = 42;
... another segment of code
}

when quickly browsing through it, you have to have a mental model of the variables defined in the preceding segment to know if it's just a temporary variable or does it mutate the objects state. Whereas this.foo = 42 makes it obvious that the objects state is mutated. And if explicit dereferencing is exclusively used, you can be sure that the variable is temporary in the opposite case.

Shorter, well factored methods make it a bit less important, but still, long term understandability trumps a little convenience while writing the code.



Related Topics



Leave a reply



Submit