Search code examples
c++inheritancesubclassing

C++ Interface Implementation and Subclassed Object Instatiation


Update The actual context is that I need to create a package to submit to NIST, trying to test a Facial Recognition Algorithm I am working on. The API to use can be found at NIST API and the project to git is at git project

Some code to make a summary of the scenario:

a.h - interface A, composed by pure virtual methods and one static method (frvt11.h at NIST project)

class A {
   public:
      virtual ~A() {}
      virtual void pure_virtual_method_a() = 0;
      virtual void pure_virtual_method_b() = 0;
      static int static_method();
}

b.h - header file for b.cpp, where interface A's methods are implemented

#include "a.h"

class B : A {
   public:
      void pure_virtual_method_a();
      void pure_virtual_method_b();
      static int static_method();
}

b.cpp - Implementation of interface A's methods.

#include "b.h"

void pure_virtual_method_a() {/*implementation*/};
void pure_virtual_method_b() {/*implementation*/};
int static_method() {/*implementation*/};

c.cpp - A file with only a main method and where I want to instantiate an object of B to use its methods.

#include "b.h"

int main(){
    B obj;
    obj.pure_virtual_method_a();
    return 0;
}

Question 1: To instantiate an object of B in c.cpp, do I need to write the header file b.h like above? That seems to be so redundant! It looks like interface A is so unnecessary :-(

Question 2: Is the code presented the right way to implement interface A, and use an object of type B?

Question 3: Do I need to declare a constructor for B in b.h and implement it in b.cpp?


Solution

  • Question 1

    You miss a semicolon after the closing curly brace, and it is better to specify that the pure virtual methods you implement in B are marked as override. This allows the compiler to emit warnings in case you forget to change any of the overridden method declarations as soon as the corresponding pure virtual methods in A would change. Thus you will end up with:

    #include "a.h"
    
    struct B : public A {
        void pure_virtual_method_a() override;
        void pure_virtual_method_b() override;
        static int static_method();
    };
    

    From this it is also clear that for the compiler to be able to declare B as a type, A needs to be declared as well. For example, how would the compiler check the override keyword if it did not yet already have a declaration of A. In case you would not have any overridden virtual methods, A still needs to be known, since the compiler needs to be able to deduce the size of B.

    Furthermore, as mentioned in the comments, declaring B as a struct allows you to drop the public keyword, since the default visibility of a struct is public in contrast with private default visibility for a class. This is the only difference between classes and structs in C++. Thus, for interfaces, using structs is more natural.

    Question 2

    Not completely. b.cpp should look something along the following lines:

    #include "b.h"
    
    void B::pure_virtual_method_a() {/*implementation*/};
    void B::pure_virtual_method_b() {/*implementation*/};
    int B::static_method() {/*implementation*/};
    

    Otherwise, you declare and define three methods in the global namespace, and the linker will complain about undefined references to the three methods of B declared in b.h.

    Furthermore, B needs to know how to derive from A, either publicly, protected, or private.

    Furthermore, it is a good idea to add include guards to the header files, to prevent the compiler from seeing the same type declaration twice when compiling an object file like a.o or b.o:

    #ifndef B_H
    #define B_H
    
    #include "a.h"
    
    struct B : public A {
      ...
    };
    
    #endif
    

    Finally, static_method() needs an implementation for A as well, static methods can not be virtual.

    Question 3

    You do not need to implement a constructor for B necessarily. In case you do not define one, the compiler will generate a default constructor for B. However, in case you define a non-default constructor for A, you will need to define one for B in case you want to construct instances of type B. The constructor can be implemented inline in the header file, or can be defined in b.cpp.