Object Oriented Programming In Good Ol’ C

C Is Simple, So Why C++?

  • struct to aggregate related things into a new type

    • Copy supported by compiler

  • Functions to add functionality

  • Pointers to pass things by reference (e.g. for modifying functionality)

  • C struct initialization

    • If used ⟶ good

    • If unused ⟶ uninitialized

struct point: Something That Looks Like A Point

  • Initialization can be done

  • But need not!

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

TEST(point_c_suite, struct_initialization)
{
    struct point p1;            // <--- uninitialized (there be dragons!)
    struct point p2 = {1,2};    // <--- good ol' C "struct initialization"

    ASSERT_EQ(p2.x, 1);
    ASSERT_EQ(p2.y, 2);

    (void)p1; // avoid "unused" warning
}

Good Style: Explicit Initialization Functions

  • C++ constructor initializes an object

  • Object referred to as this (part of C++ syntax)

  • In C, everything is explicit

  • ⟶ pointer to object (usually given as first parameter)

Default Constructor

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

void point_init_default(struct point* self)   // <--- "constructor" in C
{
    self->x = 0;
    self->y = 0;
}

TEST(point_c_suite, c_default_constructor_init)
{
    struct point p;                           // <--- uninitialized
    point_init_default(&p);                   // <--- post-birth initialization

    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
}

Alternative …

  • Something that does not let things uninitialized

  • Expensive though if objects are larger (return by copy)

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

struct point point_create_default(void)   // <--- "constructor" in C
{
    struct point p;
    p.x = 0;
    p.y = 0;
    return p;
}

TEST(point_c_suite, c_default_constructor_create)
{
    struct point p = point_create_default();   // <--- uninitialized state not possible

    ASSERT_EQ(p.x, 0);
    ASSERT_EQ(p.y, 0);
}

Constructor: initialize a struct point with its coordinates

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

struct point point_create(int x, int y)
{
    struct point p = {x,y};
    return p;
}

TEST(point_c_suite, c_constructor)
{
    struct point p = point_create(1,2);
    
    ASSERT_EQ(p.x, 1);
    ASSERT_EQ(p.y, 2);
}

Comparison Functions: (In)Equality

  • Compare two points

  • Parameters (lhs and rhs) passed by copy (a struct point is 8 bytes in size which is the size of a pointer)

  • A rather clumsy way to express == and !=

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

int /*bool*/ point_equal(point lhs, point rhs)
{
    return
        lhs.x == rhs.x &&
        lhs.y == rhs.y;
}
int /*bool*/ point_not_equal(point lhs, point rhs)
{
    return ! point_equal(lhs, rhs);
}

TEST(point_c_suite, eq_ne)
{
    const struct point p = {1,2};
    const struct point q = {3,4};

    int b;

    b = point_equal(p, q);
    ASSERT_FALSE(b);

    b = point_equal(p, p);
    ASSERT_TRUE(b);

    b = point_not_equal(p, p);
    ASSERT_FALSE(b);

    b = point_not_equal(p, q);
    ASSERT_TRUE(b);
}

Moving Points

  • Move operation modifies object

  • ⟶ passed as (non-const) pointer

  • A rather clumsy way to express +=

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

void point_move(point* self, int x, int y)
{
    self->x += x;
    self->y += y;
}

TEST(point_c_suite, move)
{
    point p = {1,2};

    point_move(&p, 3, 4);

    ASSERT_EQ(p.x, 4);
    ASSERT_EQ(p.y, 6);
}

Vector Addition and Subtraction

  • Creates a new point out of two points which are left unmodified

  • Pass by copy (again because size of struct point is rather small)

  • Return by copy

#include <gtest/gtest.h>

struct point
{
    int x;
    int y;
};

point point_add(point lhs, point rhs)
{
    struct point sum;
    sum.x = lhs.x + rhs.x;
    sum.y = lhs.y + rhs.y;
    return sum;
}

point point_sub(point lhs, point rhs)
{
    struct point diff;
    diff.x = lhs.x - rhs.x;
    diff.y = lhs.y - rhs.y;
    return diff;
}

TEST(point_c_suite, add_sub)
{
    const point p = {1,2};
    const point vec = {3,4};

    const point sum = point_add(p, vec);
    ASSERT_EQ(sum.x, 4);
    ASSERT_EQ(sum.y, 6);

    const point diff = point_sub(p, vec);
    ASSERT_EQ(diff.x, -2);
    ASSERT_EQ(diff.y, -2);
}

Distance Of Two Points

  • Points passed by copy

#include <gtest/gtest.h>

#include <math.h>

struct point
{
    int x;
    int y;
};

double point_distance(point lhs, point rhs)
{
    int a = rhs.x - lhs.x;
    int b = rhs.y - lhs.y;
    return sqrt(a*a + b*b);
}

TEST(point_c_suite, distance)
{
    const point p1 = {3,4};
    const point p2 = {5,7};
    double dst = point_distance(p1, p2);

    ASSERT_FLOAT_EQ(dst, sqrt(13));
}

Length Of A Point’s Position Vector

  • Computation does not modify the object

  • ⟶ pass by const pointer

#include <gtest/gtest.h>

#include <math.h>

struct point
{
    int x;
    int y;
};

double point_abs(const point* self)
{
    int hyp = self->x * self->x + self->y * self->y;
    return sqrt(hyp);
}

TEST(point_c_suite, abs)
{
    const point p = {3,4};
    double abs = point_abs(&p);

    ASSERT_FLOAT_EQ(abs, 5.0);
}

Summary: Is C Good Enough?

  • Members are public

    struct point p = {42, 7};
    ...
    p.x = 666;   // unwanted at times
    
    • ⟶ Bugs are only a matter of time

    • Counter argument: “Real programmers don’t write bugs”

  • Function just hang around

    • point_add() is the addition operator which belongs to struct point

    • Just nobody knows

  • Inconsistent parameter passing schema: how is the object passed?

    double point_abs(const point* self);
    point point_sub(point lhs, point rhs);
    
  • Clean/consistent initialization wanted

    • Constructor (and destructor)

    • Error checking

  • Overloaded operators - e.g. addition of two struct point objects using the addition operator +

  • Methods on Objects, like moving a point: p.abs();