Skip to main content
\(\newcommand{\doubler}[1]{2#1} \newcommand{\binary}{\texttt} \newcommand{\hex}{\texttt} \newcommand{\octal}{\texttt} \newcommand{\prog}{\texttt} \newcommand{\lt}{<} \newcommand{\gt}{>} \newcommand{\amp}{&} \)

Section15.4Structs as C++ Objects

In C++ the data that defines an instance of an object is organized as a struct. The name of the object is essentially the name of a struct variable. The class member functions have direct access to the struct's fields, even if these fields are private data members. This direct access is implemented by passing the address of the object (the struct variable) as an implicit argument to the member function. In our environment, it is passed as the first (the left-most) argument, but it does not appear in the argument list.

Let us look at the simple fraction class in Listing 15.4.1 as an example. The programs in this section assume the existence of the functions:

  • writeStr—Displays a text string on the screen.

  • getDecInt—Reads a signed integer from the keyboard and returns it.

  • putDecInt—Displays a signed integer on the screen.

// incFraction.cc
// Gets a fraction from user and increments by one
// Bob Plantz - 9 July 2016

#include "fraction.h"
extern "C" int newLine(void);

int main(void)
{
  fraction x;

  x.get();
  x.add(1);
  x.display();
  newLine();
  return 0;
}
// fraction.h
// simple fraction class
// Bob Plantz - 9 July 2016

#ifndef FRACTION_H
#define FRACTION_H

class fraction {
    int num;        // numerator
    int den;        // denominator
  public:
    fraction();     // default constructor
    void get();     // gets user's values
    void display(); // displays fraction
    void add(int);  // adds integer
};

#endif
// fraction.cc
// simple fraction class
// Bob Plantz - 9 July 2016

#include "fraction.h"
extern "C" int writeStr(char *);
extern "C" int getDecInt(void);
extern "C" int putDecInt(int);

fraction::fraction()
{
  num = 0;
  den = 1;
}

void fraction::get()
{
  // char arrays are used because writeStr takes
  //     a pointer to a C-style string.
  char numMsg[] = "Enter numerator: ";
  char denMsg[] = "Enter denominator: ";

  writeStr(numMsg);   
  num = getDecInt();
   
  writeStr(denMsg);
  den = getDecInt();
}

void fraction::display()
{
  // char array is used because writeStr takes
  //     a pointer to a C-style string.
  char over[] = "/";

  putDecInt(num);
  writeStr(over);
  putDecInt(den);
}

void fraction::add(int theValue)
{
  num += theValue * den;
}
Listing15.4.1Add 1 to user's fraction. There are three files here, incFraction.cc, fraction.h, and fraction.cc. (C++)

The fraction class is declared in the fraction.h header file, so we need to include it:

#include "fraction.h"

The fraction class contains both variable and function members. By default, members are private. Declaring the function members public allows them to be used by functions other than those within the class.

We make use of assembly language functions written earlier in the book, which are callable from C. When doing this, we need to tell the C++ compiler that these are C functions by using a prototype statement like:

extern "C" int newLine(void);

A class declaration defines a new type that the programmer can use. As with the built-in data types, we can declare a variable of type fraction:

fraction x;

In object-oriented programming, this is typically called “instantiating an object.” We can now send messages to this object. For example, the get message:

x.get();

will get values for the numerator and denominator in the x fraction from the user. This is also known as “invoking a method” or (especially in C++) “calling a member function.”

The assembly language generated by the g++ compiler Listing 15.4.2) shows the rationale for “calling a member function”.

        .arch armv6
        .file   "incFraction.cc"
        .text
        .align  2
        .global main
        .type   main, %function
main:
        .fnstart
.LFB0:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        stmfd   sp!, {fp, lr}
        .save {fp, lr}
        .setfp fp, sp, #4
        add     fp, sp, #4
        .pad #8
        sub     sp, sp, #8
        sub     r3, fp, #12         @@ address of x object
        mov     r0, r3
        bl      _ZN8fractionC1Ev    @@ fraction x;
        sub     r3, fp, #12
        mov     r0, r3
        bl      _ZN8fraction3getEv  @@ x.get();
        sub     r3, fp, #12
        mov     r0, r3
        mov     r1, #1
        bl      _ZN8fraction3addEi  @@ x.add(1);
        sub     r3, fp, #12
        mov     r0, r3
        bl      _ZN8fraction7displayEv  @@ x.display();
        bl      newLine
        mov     r3, #0
        mov     r0, r3
        sub     sp, fp, #4
        @ sp needed
        ldmfd   sp!, {fp, pc}
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
        .arch armv6
        .file   "fraction.cc"
        .text
        .align  2
        .global        _ZN8fractionC2Ev
        .type   _N8fractionC2Ev, %function
_ZN8fractionC2Ev:               @@ constructor
        .fnstart
.LFB1:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        str     fp, [sp, #-4]!
        add     fp, sp, #0
        sub     sp, sp, #12
        str     r0, [fp, #-8]
        ldr     r3, [fp, #-8]
        mov     r2, #0
        str     r2, [r3]
        ldr     r3, [fp, #-8]
        mov     r2, #1
        str     r2, [r3, #4]
        ldr     r3, [fp, #-8]
        mov     r0, r3
        sub     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr

        .section  .rodata
        .align  2
.LC0:
        .ascii  "Enter numerator: \000"
        .align  2
.LC1:
        .ascii  "Enter denominator: \000"
        .text
        .align  2
        .global _ZN8fraction3getEv
        .type   _ZN8fraction3getEv, %function
_ZN8fraction3getEv:             @@ get() member function
        .fnstart
.LFB3:
        @ args = 0, pretend = 0, frame = 48
        @ frame_needed = 1, uses_anonymous_args = 0
        stmfd   sp!, {fp, lr}
        .save {fp, lr}
        .setfp fp, sp, #4
        add     fp, sp, #4
        .pad #48
        sub     sp, sp, #48
        str     r0, [fp, #-48]
        ldr     r3, .L5
        sub     ip, fp, #24
        mov     lr, r3
        ldmia   lr!, {r0, r1, r2, r3}
        stmia   ip!, {r0, r1, r2, r3}
        ldr     r3, [lr]
        strh    r3, [ip]        @ movhi
        ldr     r3, .L5+4
        sub     ip, fp, #44
        mov     lr, r3
        ldmia   lr!, {r0, r1, r2, r3}
        stmia   ip!, {r0, r1, r2, r3}
        ldr     r3, [lr]
        str     r3, [ip]
        sub     r3, fp, #24
        mov     r0, r3
        bl      writeStr
        bl      getDecInt
        mov     r2, r0
        ldr     r3, [fp, #-48]
        str     r2, [r3]
        sub     r3, fp, #44
        mov     r0, r3
        bl      writeStr
        bl      getDecInt
        mov     r2, r0
        ldr     r3, [fp, #-48]
        str     r2, [r3, #4]
        sub     sp, fp, #4
        @ sp needed
        ldmfd   sp!, {fp, pc}
.L6:
        .align  2
.L5:
        .word   .LC0
        .word   .LC1

        .align  2
        .global _ZN8fraction7displayEv
        .type    _ZN8fraction7displayEv, %function
_ZN8fraction7displayEv:         @@ display() member function
        .fnstart
.LFB4:
        @ args = 0, pretend = 0, frame = 16
        @ frame_needed = 1, uses_anonymous_args = 0
        stmfd   sp!, {fp, lr}
        .save {fp, lr}
        .setfp fp, sp, #4
        add     fp, sp, #4
        .pad #16
        sub     sp, sp, #16
        str     r0, [fp, #-16]
        mov     r3, #47
        strh    r3, [fp, #-8]        @ movhi
        ldr     r3, [fp, #-16]
        ldr     r3, [r3]
        mov     r0, r3
        bl      putDecInt
        sub     r3, fp, #8
        mov     r0, r3
        bl      writeStr
        ldr     r3, [fp, #-16]
        ldr     r3, [r3, #4]
        mov     r0, r3
        bl      putDecInt
        sub     sp, fp, #4
        @ sp needed
        ldmfd   sp!, {fp, pc}

        .align  2
        .global _ZN8fraction3addEi
        .type   _ZN8fraction3addEi, %function
_ZN8fraction3addEi:             @@ add() member function
        .fnstart
.LFB5:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        @ link register save eliminated.
        str     fp, [sp, #-4]!
        add     fp, sp, #0
        sub     sp, sp, #12
        str     r0, [fp, #-8]
        str     r1, [fp, #-12]
        ldr     r3, [fp, #-8]
        ldr     r2, [r3]
        ldr     r3, [fp, #-8]
        ldr     r3, [r3, #4]
        ldr     r1, [fp, #-12]
        mul     r3, r1, r3
        add     r2, r2, r3
        ldr     r3, [fp, #-8]
        str     r2, [r3]
        sub     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .cantunwind
        .ident  "GCC: (Raspbian 4.9.2-10) 4.9.2"
Listing15.4.2Add 1 to user's fraction. (g++ asm)

The x.get() message passing is implemented in assembly language with a call to the _ZN8fraction3getEv function, passing the address of the x object to the function:

sub     r3, fp, #12
mov     r0, r3
bl      _ZN8fraction3getEv  @@ x.get();

This may look like it is calling another function, not the get function. The C++ compiler uses an algorithm that combines the class name with the member function name, plus the data types of any arguments to the member function. This is called name mangling. There is no standard for how this is actually done, so each compiler may do it differently.

This is not a book about C++ programming, so we will move on now. The point that is made here is that there is nothing magical about object-oriented programming. It is merely a matter of the compiler translating (perhaps even mangling) the syntax into the appropriate assembly (ultimately, machine) language.

We now consider a way to implement this same program in C, Listing 15.4.3. We need to create our own “name mangling” here to ensure that each function has a unique name within this program.

/* incFraction.c
 * Gets user's values for a fraction and
 * adds 1 to it.
 * Bob Plantz - 6 August 2016
 */

#include "fraction.h"
#include "fractionGet.h"
#include "fractionAddInt.h"
#include "fractionDisplay.h"
#include "newLine.h"

int main(void)
{
  struct fraction x;

  fraction(&x);      /* "constructor" */
  fractionGet(&x);
  fractionAddInt(&x, 1);
  fractionDisplay(&x);
  newLine();
  return 0;
}
/* fraction.h
 * A fraction "constructor" in C
 * Bob Plantz - 6 August 2016
 */

#ifndef FRACTION_H
#define FRACTION_H

struct fraction {
  int num;
  int den;
};

void fraction(struct fraction* this);

#endif
/* fraction.c
 * A fraction "constructor" in C
 * Bob Plantz - 16 August 2016
 */

#include "fraction.h"

void fraction(struct fraction* this)
{
  this->num = 0;
  this->den = 1;
}
/* fractionAdd.h
 * adds an integer to the fraction
 * Bob Plantz - 6 August 2016
 */

#ifndef FRACTION_ADD_H
#define FRACTION_ADD_H
#include "fraction.h"

void fractionAdd(struct fraction* this, int theValue);

#endif
/* fractionAddInt.c
 * adds an integer to the fraction
 * Bob Plantz - 6 August 2016
 */


#include "fractionAddInt.h"

void fractionAddInt(struct fraction* this, int theValue)
{
  this->num += theValue * this->den;
}
/* fractionDisplay.h
 * Displays a fraction in num/den format
 * Bob Plantz - 6 August 2016
 */

#ifndef FRACTION_DISPLAY_H
#define FRACTION_DISPLAY_H
#include "fraction.h"

void fractionDisplay(struct fraction* this);

#endif
/* fractionDisplay.c
 * Displays a fraction in num/den format
 * Bob Plantz - 6 August 2016
 */

#include "writeStr.h"
#include "putDecInt.h"
#include "fractionDisplay.h"

void fractionDisplay(struct fraction* this)
{
  putDecInt(this->num);
  writeStr("/");
  putDecInt(this->den);
}
/* fractionGet.h
 * Gets numerator and denominator from user.
 * Bob Plantz - 6 August 2016
 */

#ifndef FRACTION_GET_H
#define FRACTION_GET_H
#include "fraction.h"

void fractionGet(struct fraction* this);

#endif
/* fractionGet.c
 * Gets user values for a fraction
 * Bob Plantz - 6 August 2016
 */

#include "writeStr.h"
#include "getDecInt.h"
#include "fractionGet.h"

void fractionGet(struct fraction* this)
{
  writeStr("Enter numerator: ");   
  this->num = getDecInt();

  writeStr("Enter denominator: ");
  this->den = getDecInt();
}
Listing15.4.3Add 1 to user's fraction. There are nine files here. (C)

Notice the use of the this pointer in the C equivalents of the “member” functions. Its place in the parameter list coincides with the implicit argument to C++ member functions—that is, the address of the object. The this pointer is implicitly available for use within C++ member functions. Its use depends upon the specific algorithm. You should now have a good understanding of how C++ implements objects.

Subsection15.4.1Exercises

1

Write a program in assembly language that performs like the C++ program in Listing 15.4.3. That is, it will allow the user to enter the numerator and denominator of a fraction, add \(1\) to the fraction, and display the result.

Hint Solution