Classes

Overview

// Class definition
class Hobbit {
public:
    string name;
    float height; //in meters
    int age;

    Hobbit();  // Constructor
    ~Hobbit(); // Destructor
    
    void get_name() {
        cout << "This hobbit's name is " << name << endl;
    }
    void convert_height_to_cm();

private:
    //
protected:
    //
};

// Class method definitions
Hobbit::Hobbit() {
}
Hobbit::~Hobbit() {
}
void Hobbit::convert_height_to_cm() {
    height = height*100;
    cout << "Height of the hobbit is " << height << " centimeters" << endl;
}

Inheritance

Inheriting from Multiple Classes

class DerivedClass : public BaseClass1, protected BaseClass2, private BaseClass3 {
};

Inheriting from Classes Without a Default Constructor

The default constructor of each base class will be called when the derived class is instantiated. If the base class does not have a default constructor, there will be a compilation error.

To get around this, you can explicitly call whatever constructor exists for each base class in the initialization list of the derived class' constructor.

class DerivedClass : public BaseClass1, protected BaseClass2 {
    DerivedClass() : BaseClass1(0, "hello"), BaseClass2(17, "world") {}
};

Inheritance Keywords

Base Class

  • public: accessible to anything outside or inside the class

  • protected: members will be available to derived classes, but private otherwise.

  • private: only accessible to methods within the class

  • friend: friend class can access private and protected members of class in which friend in declared.

Derived Class Signature:

  • public Inheritance ("as-is"):

    • public base -> public derived

    • protected base -> protected derived

    • private base -> not accessible in derived

  • protected inheritance:

    • public base -> protected derived

    • protected base -> protected derived

    • private base -> not accessible in derived

  • private inheritance:

    • public base -> private derived

    • protected base -> private derived

    • private base -> not accessible in derived

Friend Classes

Can declare under any of the public, private, or protected base class keywords; doesn't make any difference.

class MyClass {
private:
    friend class OtherClassThatWantsPrivateAccess;
};

Unit Testing Private/Protected Class Methods

The convention is to only test public methods of a class. If it is insufficient to only test public methods, it is said that your class needs restructuring.

However, there are some cases where you might want to test private/protected methods of a class. Perhaps an example would be if you are incrementally improving the robustness of that class and you want to do it in pieces, without having to refactor the entire thing at once.

Below are two strategies.

  • Have the test fixture class inherit publicly from the class being tested

  • Any public and protected methods in the class under test will be available to the test fixture class, as long as the class under test is inherited publicly.

  • private methods will not be accessible

  • Declare the test fixture class as a friend class inside the class under test. It doesn't matter which privacy tag it sits under (public, protected, or private).

    • friend class path::to::test::TestFixtureClass;

  • The test fixture class will then have access to all public, protected, and private members and methods of the class under test.

  • Add a forward declaration of the test fixture class to the file containing the class under test. Otherwise the friend class declaration will fail because it doesn't have a definition of the test class.

namespace path::to::test {
    class TestFixtureClass;
}
  • If you're using Google Test for C++, only the test fixture class will have access to the private members. The test cases (TEST, TEST_F, TEST_P, etc.) will not. To get around this, you have to create helper functions in the test fixture class that the test cases will call. The helpers will simply call the private/protected methods being tested and pass the return values back.

circle.h
namespace path::to::test {
    class TestFixtureClass;
}

class Circle {
private:
    double Circumference(double diam);
    
    friend class MyTestFixture;
};
circle_test.cpp
class MyTestFixture : public ::testing::Test {
    // CircleClass is the class whose private method
    // Circumference() is being tested.
    double CircumferenceHelper(const CircleClass& circle, const double& diam) {
        return circle.Circumference(diam);
    }
};
  • Finally, you call the helper function in the test case.

circle_test.cpp
TEST_F(MyTestFixture, MyTestCase) {
    double diam = 10;
    EXPECT_EQ(CircumferenceHelper(diam), diam * 3.14) << "Circumference failed.";
}

Abstract Interface Class

An abstract interface class is a purely virtual base class that just defines the public interface (public types, and public methods).

Concrete classes would inherit from the abstract interface class and contain all the implementation.

This architecture has benefits:

  • Makes it possible to mock the class in tests

  • Clearly defines the public interface / contract

  • Keeps interface separate from implementation

In Java, it is very common to always have an abstract interface class for every class.

Abstract Interface Class:

  • Should contain only virtual public methods.

  • All virtual public methods must be set equal to zero. This tells the compiler that they will not be implemented in the base class. This prevents instances of it being created.

  • Must contain a virtual destructor equal to default.

class IMyClass {
public:
    struct MyStruct {
        int dog;
        int cat;
    };

    virtual ~IMyClass() = default;
    
    virtual void Method1(int a, int b) = 0;
    
    virtual int Method2(int a, int b) = 0;
};

Concrete Implementation Class:

class MyClass : public IMyClass {
    MyClass() = default;
    ~MyClass() override {}
    
    void Method1(int a, int b) override {}
    
    int Method2(int a, int b) override {}
};

The type used for storing an instance, or passing an instance through function arguments or returns should always be the abstract interface class IMyClass.

When you create an instance of the class to use, it should always be an instance of the concrete implementation class, MyClass.

Because MyClass inherits from IMyClass, the MyClass instance can be stored or passed anywhere as a IMyClass type. The types are compatible.

Last updated