this & Temporary Objects

this & Temporary Objects

Here is the next stage of our saga about C++ classes…

The this Pointer

When you invoke a (non-static) class member function:

Complex c(1.0, 2.0);
 // ...
c.Print();

within the member function, there is available a pointer called this.

The this pointer for a class T is implicitly defined as:

T * const this;  // means what? Can you say it in words?

(later, we’ll see cases where this changes)

this is implicitly initialized to point to the instance of the class that was used to invoke the member function.

For example, assuming the existence of a String class, the call:

String s1("Hello!");
String s2("");
s2 = s1.Left(5);

is equivalent to something like:

String s1("Hello!");
String s2("");
s2 = Left(&s1, 5);

In other words, there is a hidden ‘parameter’ supplied to the member function behind the scenes.

Access via this is only within class scope, since it’s only available from within member functions of the class.

In most cases, references via this are implied. 

For example:

Complex::Complex(double r)
{
  real = r;
  imag = 0.0;
}

is equivalent to:

Complex::Complex(double r)
{
  this->real = r;
  this->imag = 0.0;
}

However, there are cases where you need to use this explicitly.  We’ll see some of these in a short while.

Temporary Objects

There are cases where the compiler needs to construct temporary objects.

For example:

Here’s a very simple Complex class, together with its class method functions, annotated to produce output at suitable times.

//
//  Complex.h
//  Temporary Objects
//
//  Created by Bryan Higgs on 8/19/24.
//

#ifndef Complex_h
#define Complex_h

class Complex
{
public:
  Complex(double r, double i = 0.0);
  Complex(const Complex &src);
  ~Complex();
  void Print() const;
  
  double m_r, m_i; // Real, imaginary
  
   // ...
};

#endif /* Complex_h */
//
//  Complex.cpp
//  Temporary Objects
//
//  Created by Bryan Higgs on 8/19/24.
//

#include <iostream>

#include "Complex.h"

Complex::Complex(double r, double i)
{
  std::cout << "Constructing instance of Complex from doubles ["
            << r << "," << i << "]" << std::endl;
}

Complex::Complex(const Complex &src)
{
  std::cout << "Constructing instance of Complex using copy constructor from ";
  src.Print();
  std::cout << std::endl;
}

Complex::~Complex()
{
  std::cout << "Destroying instance of Complex ";
  this->Print();
  std::cout << std::endl;
}

void Complex::Print() const
{
  std::cout << "[" << m_r << "," << m_i << "] ";
}

And here’s a test program to try things out, with its own annotated output:

//
//  TestComplex.cpp
//  Temporary Objects
//
//  Created by Bryan Higgs on 8/19/24.
//

#include <iostream>
#include "Complex.h"

Complex f(Complex c)
{
  std::cout << "f: Complex argument c: ";
  c.Print();
  std::cout << std::endl;
  // Do something...
  return c;
}

int main()
{
  Complex a(1.0);
  Complex b = f(Complex(2.0));
  a = f(a);
  return 0;
}

Here’s the output the program produces:

Constructing instance of Complex from doubles [1,0]
Constructing instance of Complex from doubles [2,0]
f: Complex argument c: [0,6.95161e-310] 
Constructing instance of Complex using copy constructor from [0,6.95161e-310] 
Destroying instance of Complex [0,6.95161e-310] 
Constructing instance of Complex using copy constructor from [6.95167e-310,6.95161e-310] 
f: Complex argument c: [0,0] 
Constructing instance of Complex using copy constructor from [0,0] 
Destroying instance of Complex [0,6.95161e-310] 
Destroying instance of Complex [0,0] 
Destroying instance of Complex [6.95161e-310,6.95161e-310] 
Destroying instance of Complex [0,6.95161e-310] 
Program ended with exit code: 0

Can you explain this output? Can you see any strangenesses?

What’s going on?

How many temporary objects (i.e. without a name) can you identify above?

Creation and Destruction of Temporary Objects

There are two major questions about temporary objects:

  • When are they created?
  • When are they destroyed?

Creation of Temporary Objects

In this case:

Complex f(Complex c)
{
  // ...
}
// ...
Complex a(1.0);	
a = f(a);
  • A temporary object is created for the argument on the call to f().
  • A temporary object is created for the value returned from f().

Why?  Is this desirable?

How might one avoid this?

When a temporary object is created:

  • The compiler uses the appropriate constructor to construct the object.
  • If there is no appropriate constructor, then compilation will fail with an error.

For example, if we remove the Complex(double r, double i) constructor from the previous example, we get:

//
//  Complex.h
//  Temporary Objects
//
//  Created by Bryan Higgs on 8/19/24.
//

#ifndef Complex_h
#define Complex_h

class Complex
{
public:
  // Complex(double r, double i = 0.0);
  Complex(const Complex &src);
  ~Complex();
  void Print() const;
  
  double m_r, m_i; // Real, imaginary
  
   // ...
};

#endif /* Complex_h */
//
//  TestComplex.cpp
//  Temporary Objects
//
//  Created by Bryan Higgs on 8/19/24.
//

#include <iostream>
#include "Complex.h"

Complex f(Complex c)
{
  std::cout << "f: Complex argument c: ";
  c.Print();
  std::cout << std::endl;
  // Do something...
  return c;
}

int main()
{
  Complex a(1.0); // No matching constructor for initialization of 'Complex'
  Complex b = f(Complex(2.0)); // No matching conversion for functional-style cast from 'double' to 'Complex'
  a = f(a);
  return 0;
}

Destruction of Temporary Objects

When is a temporary object destroyed?

  • Back in the early days of C++, this used to be subject to the whims of each vendor, but that was found totally unacceptable, since it prevented programmers from writing portable code.
  • Now, the ISO C++ committee has specified that the lifetime of a temporary object is until the end of the statement (more or less) in which it is created.

This can cause problems, if you’re not careful.

Here’s a really dumb string class to illustrate the dangers:

//
//  MyString.h
//  Temporary Objects (MyString example)
//
//  NOTE: A really dumb string class!
//
//  Created by Bryan Higgs on 8/20/24.
//

#ifndef MyString_h
#define MyString_h

class MyString
{
public:
  MyString(const char *string);
  
  void Print();

  ; // ...
  char m_string[10]; // Limited string size
};

#endif /* MyString_h */
//
//  MyString.cpp
//  Temporary Objects (MyString example)
//
//  Created by Bryan Higgs on 8/20/24.
//

#include <iostream>
#include <assert.h> // For assert macro
#include <stdio.h>  // for strlen, strcpy
#include "MyString.h"

MyString::MyString(const char *string)
{
  // It's dangerous to use bare C string handling...
  assert( string != NULL ); // Valid pointer?
  assert( (strlen(string) < 10) ); // Not too long?
  
  strcpy(m_string, string);
}

void MyString::Print()
{
  std::cout << "MyString: '" << m_string << "'" << std::endl;
}
//
//  main.cpp
//  Temporary Objects (MyString example)
//
//  Created by Bryan Higgs on 8/20/24.
//

#include <iostream>
#include "MyString.h"

int main(int argc, const char * argv[]) 
{
  MyString str = MyString("Hi there!");
  str.Print();  // OK
  MyString *ptr = &MyString("Hello!");
  ptr->Print();    // Surprise!
  return 0;
}

which, when I was first creating this course, produced the following output on my machine, using Microsoft Visual C++:

Hi there!▌▌▌▌▌▌▌▌▌▌▌

In other words, garbage.

Now, using an up to date C++ compiler, the compiler catches the problem at compile time, with the error:

Taking the address of a temporary object of type 'MyString'

Summary

Well, two more topics down:

  • this
  • Temporary Objects

Lots more to learn!

Onward!

Index