Section 15.6 struct
s 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; }
// 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
// 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; }
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
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
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; }
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
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; }
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
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; }
/* 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
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); }
/* 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
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(); }
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.