0

I'm still learning how the abstract classes work and I want know if I'm on the right track or not.

This is my simplified program :

class CPerson
{
public:
  CPerson() = default;
  virtual int getMat() = 0;
};

class CStudent : public CPerson
{
public:
  CStudent() = default;
  CStudent(int MatriculationNr) { this->MatriculationNr = MatriculationNr;}

  int getMat() { return MatriculationNr;}

private:
  int MatriculationNr;
};

class CTeacher : public CPerson
{
  public:
  CTeacher() = default;
  int getMat(){ return 0;} 

private:
  std::string name;
};

int main()
{
  std::vector<CPerson*> test;

  test.push_back(new CStudent(9898));
  CTeacher *a = new CTeacher();


  return 0;
}

In class CTeacher, I don't have the same private variable like CStudent (MatriculationNr) so I returned 0. The program is working normally. But is what I'm doing here correct or not?

Another question related to the abstract classes : Assuming we use virtual int& getMat() = 0; (with a reference), what should we return in CTeacher class? 0 will not work in this case, right?

Should we initalize a variable with 0 so that we can return it in this function, or is there a better implementation?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Samir
  • 235
  • 1
  • 10
  • Has to say CTeacher* a = new CTeacher(); – ByronB Nov 27 '20 at 23:02
  • Yeah copy mistake otherwise the implementation of abstract classes is correct ? – Samir Nov 27 '20 at 23:04
  • One question per question please. – Asteroids With Wings Nov 27 '20 at 23:04
  • Can you explain what `getMat` means from a person's perspective, without involving specifically students or teachers? If not, then this method should not be there, Also please make sure that every polymorphic class has a virtual destructor. – n. m. could be an AI Nov 27 '20 at 23:06
  • @n.'pronouns'm. From a person's perspective it doesn't mean anything . It's only valid when CStudent is involved and since Student has to inherit from Person then I have to initialize a pure virtual method to CPerson . – Samir Nov 27 '20 at 23:10
  • 1
    This means your design is buggy. A class should not have methods which are only meaningful for some subclasses and not others, – n. m. could be an AI Nov 27 '20 at 23:12
  • Good reading on n.'pronouns'm's comment: [What is an example of the Liskov Substitution Principle?](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle) – user4581301 Nov 27 '20 at 23:19
  • If they are available to you, [use `std::unique_ptr`](https://en.cppreference.com/w/cpp/memory/unique_ptr) to manage the [ownership](https://stackoverflow.com/questions/49024982/what-is-ownership-of-resources-or-pointers) of the objects stored in the `vector` – user4581301 Nov 27 '20 at 23:21
  • 1
    One question per stackoverflow.com question, please. As far as whether a particular program "is correct or not", that would be a question for https://codereview.stackexchange.com/. As far as to what various subclasses should return for a particular method, this is a question that only you can answer yourself, since only you know what your program should do in this case. If a method is declared as returning a reference to an `int`, then this is what it should return. Whether the method is virtual, or not, is irrelevant. C++ requires all methods and function to return values of matching type. – Sam Varshavchik Nov 27 '20 at 23:24
  • About all that can be said is that your code will compile and run without diagnostics, which is not a definitive test of good code. However, it demonstrates bad design (a base class providing functionality that is not even relevant to a derived class), bad practice (objects created with operator `new` not being released with corresponding operator `delete`), and (if you were to correct the code to release the objects using operator `delete`) it would have undefined behaviour, since the base class does not have a `virtual` destructor. – Peter Nov 28 '20 at 00:19

2 Answers2

1

Below code sample should answer your question in a rather modern C++ way.

    #include <iostream>
    #include <string>
    #include <vector> 
    #include <memory>
    


    // I have added a `std::string name` to the CPerson class and 
    // a `std::string subject` to the CTeachers class 
    // so both derived classes, CStudent and CTeacher have a name 
    // of the person involved and each derived class has
    // something only it needs, MatrikulationNr for CStudent and 
    // Subject of teaching for CTeacher in order to deliver a more 
    // complete and more clearifying answer.



    class CPerson {

        int dummyMatriculationNr{ 0 };
        std::string dummySubject{ "noTeacher" };

        protected:

        std::string name;

        public:

        std::string getName() { return name; }
        virtual int& getMat() { return dummyMatriculationNr; }
        virtual std::string getSubject() { return dummySubject; }

    };
        


    class CStudent : public CPerson {

        int MatriculationNr{ 0 };

        public:   

        CStudent() = delete; // we dont want anyone using this constructor

        explicit CStudent(std::string name, int MatriculationNr) :  
                                MatriculationNr{ MatriculationNr } {
            this->name = name;
        }    

        int& getMat() { return MatriculationNr; }

    };
      

  
    class CTeacher : public CPerson {

        std::string subject{ "" }; // Subject of teaching  

        public:

        CTeacher() = delete; 

        explicit CTeacher(std::string name, std::string subject) : 
                                                subject{ subject } { 
            this->name = name;
        }

        std::string getSubject() { return subject; }

    };
    
    

    int main() {
    
        std::vector<std::unique_ptr<CPerson>> vp;
        
        vp.push_back(std::make_unique<CStudent>("aStudentsName", 8989 ));// or emplace_back
        vp.push_back(std::make_unique<CTeacher>("aTeachersName", "mathematics")); 
    
        for (auto& e : vp) 
        std::cout << "Name: " << e->getName() << " MatrNo: " << e->getMat() 
                  << " TeachingSubject: " << e->getSubject() << std::endl;
    
    }

I hope above sample answers your question. However, using the keyword virtual creates a virtual function table, often called vtable, at runtime which costs performance and is considered not to be high performance computing anymore.

Its also confusing to have a getMat() function available in all derived classes when you need it only in one, the CStudents derived class. Although meaningless in any other class, this function still returns some dummy value there. That can be irritating. Same for the getSubject() function in CTeacher. See the output:

Name: aStudentsName MatrNo: 8989 TeachingSubject: noTeacher
Name: aTeachersName MatrNo:    0 TeachingSubject: mathematics

Consider to solve your question without any keyword virtual and having getMat() and int MatriculationNr in CStudents only and not in the base class at all. I know it is tempting to use virtual but its something to rather avoid as long as possible. For example, MFC, Microsoft Foundation Classes, the maybe biggest class inheritance project ever written, did not use virtual at all! Consider the following code as an example:

#include <iostream>
#include <string>
#include <vector>
#include <variant>

// a code version without virtual

class CPerson {

protected:

    std::string name;

    public:

    std::string getName() { return name; }

};



class CStudent : public CPerson {

    int MatriculationNr{ 0 };

    public:

    CStudent() = delete; // we dont want anyone using this constructor

    explicit CStudent(std::string name, int MatriculationNr) : MatriculationNr{ MatriculationNr } {
        this->name = name;
    }

    int& getMat() { return MatriculationNr; }

};



class CTeacher : public CPerson {

    std::string subject{ "" }; // Subject of teaching

    public:

    CTeacher() = delete;

    explicit CTeacher(std::string name, std::string subject) : subject{ subject } {
        this->name = name;
    }

    std::string getSubject() { return subject; }

};



int main() {

    std::vector<CStudent> vs; // auto-deleted through RAII
    std::vector<CTeacher> vt; // and good for serialisation and or database communication

    vs.push_back(CStudent{ "aStudentsName", 9898 });
    vt.push_back(CTeacher{ "aTeachersName", "mathematics" });
    
    for (auto s : vs)
        std::cout << s.getName() << " " << s.getMat() << std::endl;

    for (auto t : vt)
        std::cout << t.getName() << " " << t.getSubject() << std::endl << std::endl;

    // and here we are done already,
    // not listing the two different types in one vector
    // but just using a vector for each derived class
    //
    // but lets try put them now into one vector
    // using a more modern way through std::variant
    // which keps all data in the vector and not only the 
    // CPerson part.


    std::vector<std::variant<CStudent, CTeacher>> people;

    // we could, for example, copy from above vectors
    for (auto e : vs) 
        people.push_back(e);

    for (auto e : vt)
        people.push_back(e);

    // we could insert new ones
    people.push_back(CStudent { "aStudentsName1", 9899 });
    people.push_back(CTeacher { "aTeachersName1", "physics" });

    // and take that vector apart again
    std::cout << std::endl << "Output from vector of variant:" << std::endl;
    for (auto& e : people)
        if (std::holds_alternative<CStudent>(e)) {
            CStudent& s = std::get<CStudent>(e);
            std::cout << s.getName() << " " << s.getMat() << std::endl;
        }
        else if (std::holds_alternative<CTeacher>(e)) {
            CTeacher& s = std::get<CTeacher>(e);
            std::cout << s.getName() << " " << s.getSubject() << std::endl;
        }

}

There are numerous ways to achieve the goal to avoid virtual and I hope you did enjoy the above.

ByronB
  • 100
  • 7
-1

Everything in the code above seems alright. Just make sure to delete both objects after using them (the CStudent and the CTeacher).

Ark1409
  • 25
  • 6