Skip to main content

Section 15.6 structs 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 used in the program in Listing 15.6.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
// 2017-09-29: Bob Plantz

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

int main(void)
{
  fraction x;

  x.get();
  x.add(1);
  x.display();
  newLine();
  return 0;
}
Listing 15.6.1. Program to add \(1\) to user's fraction. The fraction is an instance of the object class defined in Listing 15.6.2. (C++)
// fraction.h
// simple fraction class
// 2017-09-29: Bob Plantz

#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
Listing 15.6.2. Our fraction class has two instance variables for the numerator and denominator, a constructor, and three member functions. (C++)
// fraction.cc
// simple fraction class
// 2017-09-29: Bob Plantz

#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;
}
Listing 15.6.3. The fraction constructor and member functions are implemented in one file. (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.6.4) shows the rationale for “calling a member function”.

        .arch   armv6
        .file   "incFraction.cc"
        .text
        .align  2
        .global main
        .syntax unified
        .arm
        .fpu vfp
        .type   main, %function
main:
        .fnstart
.LFB0:
        @ args = 0, pretend = 0, frame = 8
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {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     r1, #1
        mov     r0, r3
        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
        pop     {fp, pc}
        .fnend
        .size   main, .-main
        .ident  "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
        .section  .note.GNU-stack,"",%progbits
Listing 15.6.4. The main function in Listing 15.6.1 call the constructor and member functions, passing the address of the fraction object, which is implemented as a struct. (g++ asm)
        .arch   armv6
        .file   "fraction.cc"
        .text
        .align    2
        .global _ZN8fractionC2Ev
        .syntax unified
        .arm
        .fpu    vfp
        .type   _ZN8fractionC2Ev, %function
_ZN8fractionC2Ev:
        .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
        add     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .cantunwind
        .fnend
        .size   _ZN8fractionC2Ev, .-_ZN8fractionC2Ev
        .global _ZN8fractionC1Ev
        .set    _ZN8fractionC1Ev,_ZN8fractionC2Ev
        .section  .rodata
        .align  2
.LC0:
        .ascii  "Enter numerator: \000"
        .align  2
.LC1:
        .ascii  "Enter denominator: \000"
        .text
        .align        2
        .global _ZN8fraction3getEv
        .syntax unified
        .arm
        .fpu    vfp
        .type   _ZN8fraction3getEv, %function
_ZN8fraction3getEv:
        .fnstart
.LFB3:
        @ args = 0, pretend = 0, frame = 48
        @ frame_needed = 1, uses_anonymous_args = 0
        push  {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, .L4
        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, .L4+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]
        nop
        sub     sp, fp, #4
        @ sp needed
        pop     {fp, pc}
.L5:
        .align  2
.L4:
        .word   .LC0
        .word   .LC1
        .fnend
        .size   _ZN8fraction3getEv, .-_ZN8fraction3getEv
        .align  2
        .global _ZN8fraction7displayEv
        .syntax unified
        .arm
        .fpu    vfp
        .type   _ZN8fraction7displayEv, %function
_ZN8fraction7displayEv:
        .fnstart
.LFB4:
        @ args = 0, pretend = 0, frame = 16
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {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
        nop
        sub     sp, fp, #4
        @ sp needed
        pop     {fp, pc}
        .fnend
        .size   _ZN8fraction7displayEv, .-_ZN8fraction7displayEv
        .align    2
        .global _ZN8fraction3addEi
        .syntax unified
        .arm
        .fpu vfp
        .type   _ZN8fraction3addEi, %function
_ZN8fraction3addEi:
        .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]
        nop
        add     sp, fp, #0
        @ sp needed
        ldr     fp, [sp], #4
        bx      lr
        .cantunwind
        .fnend
        .size   _ZN8fraction3addEi, .-_ZN8fraction3addEi
        .ident  "GCC: (Raspbian 6.3.0-18+rpi1) 6.3.0 20170516"
        .section  .note.GNU-stack,"",%progbits
Listing 15.6.5. The fraction class constructor and member functions access the instance variables through the address of the object (struct). (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.6.6. 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.
 * 2017-09-29: Bob Plantz
 */

#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;
}
Listing 15.6.6. Program to add \(1\) to user's fraction. Uses struct to emulate C++ object. (C)
/* fraction.h
 * A fraction "constructor" in C
 * 2017-09-29: Bob Plantz
 */

#ifndef FRACTION_H
#define FRACTION_H

struct fraction {
  int num;
  int den;
};

void fraction(struct fraction* this);

#endif
Listing 15.6.7. Definition of fraction struct. (C)
/* fraction.c
 * A fraction "constructor" in C
 * 2017-09-29: Bob Plantz
 */

#include "fraction.h"

void fraction(struct fraction* this)
{
  this->num = 0;
  this->den = 1;
}
Listing 15.6.8. “Constructor” of fraction “object”; stores initial values in the struct fields. (C)
/* fractionAdd.h
 * adds an integer to the fraction
 * 2017-09-29: Bob Plantz
 */

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

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

#endif
Listing 15.6.9. Header file for fractionAddInt function. (C)
/* fractionAddInt.c
 * adds an integer to the fraction
 * 2017-09-29: Bob Plantz
 */


#include "fractionAddInt.h"

void fractionAddInt(struct fraction* this, int theValue)
{
  this->num += theValue * this->den;
}
Listing 15.6.10. Adds an integer to a fraction, leaving the result as an improper fraction. (C)
/* fractionDisplay.h
 * Displays a fraction in num/den format
 * 2017-09-29: Bob Plantz
 */

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

void fractionDisplay(struct fraction* this);

#endif
Listing 15.6.11. Header file for the fractionDisplay function. (C)
/* fractionDisplay.c
 * Displays a fraction in num/den format
 * 2017-09-29: Bob Plantz
 */

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

void fractionDisplay(struct fraction* this)
{
  putDecInt(this->num);
  writeStr("/");
  putDecInt(this->den);
}
Listing 15.6.12. Displays the numerator and denominator of a fraction. (C)
/* fractionGet.h
 * Gets numerator and denominator from user.
 * 2017-09-29: Bob Plantz
 */

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

void fractionGet(struct fraction* this);

#endif
Listing 15.6.13. Header file for the fractionGet function. (C)
/* fractionGet.c
 * Gets user values for a fraction
 * 2017-09-29: Bob Plantz
 */

#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();
}
Listing 15.6.14. Prompts for and reads a numerator and denominator from the keyboard. (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.