Classes, Inheritance, Abstract Classes

I would like to figure out classes, inheritance, and polymorphism once and for all, I will combine all my questions and code about it in this post making it easy for me to document and lookup if i need to. I will post code about each category listed in the title.

PART 1: Classes

I made a bank class just to test my knowledge. I went all out on it and made it actually work. There is no error checking or anything but that's not the point, the main focus is the class and mainly if i created and used the class the right way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <iostream>
#include <string>
#include <vector>
#include <limits>

using namespace std;

class Bank
{
    public:
        Bank();
        ~Bank();

        void AccountInformation();
        int DisplayBalance() const{return currentBalance;};
        void CreateNewAccount();
        void Deposit();
        void Withdraw();
        int AccountMenu();

    private:
        int bankAccountNumber = 0;
        int currentBalance = 0;
        int transactionAmount = 0;
        string accountHolderName = "Default";
        int accountHolderAge = 0;
        int accountPin = 0;
};

Bank::Bank()
{

}

Bank::~Bank()
{

}

void Bank::Withdraw()
{
    cout << "How much would you like to withdraw?" << endl;

    int withdrawAmount = 0;

    cin >> withdrawAmount;

    if(currentBalance >= withdrawAmount)
    {
        cout << "You withdrew: $" << withdrawAmount << endl;
    }
    else if(currentBalance < withdrawAmount)
    {
        cout << "You do not have enough money in your account" << endl;
    }
}

void Bank::Deposit()
{
    cout << "How much would you like to deposit?" << endl;

    int depositAmount = 0;

    cin >> depositAmount;

    currentBalance += depositAmount;

    cout << "Your account now has a balance of $" << DisplayBalance() << endl;
}

void Bank::AccountInformation()
{
    cin.ignore();

    cout << "Please enter your name and account pin." << endl;

    string enterName;
    int enterPin;

    cout << "Name: ";
    getline(cin, enterName);

    cout << "Pin: ";
    cin >> enterPin;

    if(enterName == accountHolderName && enterPin == accountPin)
    {
        AccountMenu();
    }
    else
    {
        cout << "You entered a wrong name or pin number." << endl;
    }
}

int Bank::AccountMenu()
{
    Bank bank;

    cin.ignore();

    cout << "Welcome " << accountHolderName << " what would you like to do?" << endl;

    cout << "\n1) Withdraw Money" << endl;
    cout << "2) Deposit Money" << endl;
    cout << "3) Exit" << endl;

    int choice = 0;

    cin >> choice;

    switch(choice)
    {
        case 1:
            Withdraw();
            break;
        case 2:
            Deposit();
            break;
        case 3:
            return 0;
            break;
        default:
            cout << "Error" << endl;
    }

    return 0;
}

void Bank::CreateNewAccount()
{
    cin.ignore();

    cout << "Please enter your first name." << endl;

    getline(cin, accountHolderName);

    cout << "Thank you " << accountHolderName << " now please enter your age." << endl;
    cin >> accountHolderAge;

    cout << "\nThank you " << accountHolderName << ". Please enter a pin number you want to use." << endl;
    cin >> accountPin;

    cout << "Ok " << accountHolderName << " your age is " << accountHolderAge << " and your pin is " << accountPin << ". Is this ok?" << endl;

    int choice = 0;

    cout << "\n1)Yes" << endl;
    cout << "2) No" << endl;
    cin >> choice;

    if(choice == 1)
    {
        cout << "Thank you " << accountHolderName << " your account has now been created! Don't forget your pin or you wont be able to login." << endl;
    }
    else if(choice == 2)
    {
        cout << "We're sorry we could not create your account at this time" << endl;
    }
}

int main()
{
    Bank bank;

    int choice = 0;

    do
    {
        cout << "Would you like to create an account or access an existing account?\n" << endl;

        cout << "1) Create a new account." << endl;
        cout << "2) Access an existing account.\n" << endl;

        cin >> choice;

        if(choice == 1)
        {
            bank.CreateNewAccount();
        }
        else if(choice == 2)
        {
            bank.AccountInformation();
        }
    }while(choice != -1);

    return 0;
}
Last edited on
Your Bank has only one account/balance. I can create a new account, deposit some money, create a different account and withdraw money that the latest account never did deposit. If I guess ("default", 0), I can use account without creating it.


The use of class is quite ok, but the logical "real life" meaning of terms "Bank" and "Account" do not match what your program does.

What if I:
1
2
3
Bank bank(0, 0, 0, "Default", 0, 0);
bank.CreateNewAccount();
Bank swiss( bank );



Why is the constructor implemented inline, while all the other methods are outside?
I changed the code. I initialized the variables in the class as c++11 allows it, and I hate typing out all that stuff in class constructors, I just hate it more than words can tell. What should I do to make the class better? I just used it to hold and group similar variables and functions, thats what I should do right?
To go off your saying you dislike typing so much in the constructor, if the constructor/destructor is empty, you can replace the constructor/destructor declarations with
1
2
Bank() = default;
~Bank() = default;

and then remove the definitions, i.e:
1
2
3
4
5
6
7
8
9
Bank::Bank()
{

}

Bank::~Bank()
{

}

Or simply omit them. But using ... = default; is better IMO since it is more explicit in conveying your intent.
Initializing in the class declaration is fine. I do it as well. That has the added benefit of allowing users to know the initial values without diving into the implementation.
Is there a way to initialize all members of a class without using the constructor? I find initializing them through the constructor to be too tedious, particularly when you have tons of member variables. I'm using the C++ 11 way which i think is muuuuuuuuuch better, but i dont know if this will cause any problems down the line, as I plan to use this way of initializing class variables almost exclusively.
Last edited on
For most cases, you can default-initialize your variables in the declaration.
But not always:
For members that cannot be default-initialized, such as members of reference and const-qualified types, member initializers must be specified.
(see http://en.cppreference.com/w/cpp/language/initializer_list)

So a class like
1
2
3
4
5
class MyClass
{
  ...
  int &m_SomeRef;
};

would have to have a constructor to initialize m_SomeRef (more specifically, it must be initialized in the member initializer list)
like
1
2
3
4
5
6
7
8
9
class MyClass
{
  MyClass(int &Ref);
  ...
  int &m_SomeRef;
};

MyClass::MyClass(int &Ref) : m_SomeRef(Ref) //Member initializer list
{ }



For the class you have, a constructor is not necessary to initialize your variables, since none are a reference or const-qualified type.
For the purpose of being explicit, I would still put
1
2
Bank() = default;
~Bank() = default;
in the declaration, but that may come down to personal preference.

TL;DR: It depends. If you have no variables that are of a reference or const-qualified types, then yes; you can assign a default value in the declaration. If you do, then said reference or const-qualified variables must be initialized in the constructor's member initializer list.
I see, thats what I would rather use the constructor initializer list for is things that HAVE to be initialized in that IL. Using it to initalize every single variable when it can just be done at its declaration point just seems like a waste of time and whitespace, but maybe thats just me. I try to make my code do what i want in as little code as possible if i know how/spot the problem.

To go back to my Class, what exactly could i do to make it better? to be more object oriented? You mentioned that I only have one account/balance and that it doesnt match what my program does, could you please show me with some code what would need to be changed?

I did make some changes to it. Here is the new code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#include <iostream>
#include <string>
#include <vector>
#include <limits>

using namespace std;

class Bank
{
    public:
        Bank() = default;
        ~Bank() = default;

        void AccountInformation();
        int AccountMenu();
        void MainMenu();

    private:
        int bankAccountNumber = 0;
        int currentBalance = 0;
        int transactionAmount = 0;
        string accountHolderName = "Default";
        int accountHolderAge = 0;
        int accountPin = 0;

    private:
        int DisplayBalance()const{return currentBalance;}
        void CreateNewAccount();
        void Deposit();
        void Withdraw();
};

void Bank::Withdraw()
{
    cout << "How much would you like to withdraw?" << endl;

    int withdrawAmount = 0;

    cin >> withdrawAmount;

    if(currentBalance >= withdrawAmount)
    {
        cout << "You withdrew: $" << withdrawAmount << endl;
    }
    else if(currentBalance < withdrawAmount)
    {
        cout << "You do not have enough money in your account" << endl;
    }
}

void Bank::Deposit()
{
    cout << "How much would you like to deposit?" << endl;

    int depositAmount = 0;

    cin >> depositAmount;

    currentBalance += depositAmount;

    cout << "Your account now has a balance of $" << DisplayBalance() << endl;
}

void Bank::AccountInformation()
{
    cin.ignore();

    cout << "Please enter your name and account pin." << endl;

    string enterName;
    int enterPin;

    cout << "Name: ";
    getline(cin, enterName);

    cout << "Pin: ";
    cin >> enterPin;

    if(enterName == accountHolderName && enterPin == accountPin)
    {
        AccountMenu();
    }
    else
    {
        cout << "You entered a wrong name or pin number." << endl;
    }
}

int Bank::AccountMenu()
{
    Bank bank;

    cin.ignore();

    cout << "Welcome " << accountHolderName << " what would you like to do?" << endl;

    cout << "\n1) Withdraw Money" << endl;
    cout << "2) Deposit Money" << endl;
    cout << "3) Exit" << endl;

    int choice = 0;

    cin >> choice;

    switch(choice)
    {
        case 1:
            Withdraw();
            break;
        case 2:
            Deposit();
            break;
        case 3:
            return 0;
            break;
        default:
            cout << "Error" << endl;
    }

    return 0;
}

void Bank::CreateNewAccount()
{
    cin.ignore();

    cout << "Please enter your first name." << endl;

    getline(cin, accountHolderName);

    cout << "Thank you " << accountHolderName << " now please enter your age." << endl;
    cin >> accountHolderAge;

    cout << "\nThank you " << accountHolderName << ". Please enter a pin number you want to use." << endl;
    cin >> accountPin;

    cout << "Ok " << accountHolderName << " your age is " << accountHolderAge << " and your pin is " << accountPin << ". Is this ok?" << endl;

    int choice = 0;

    cout << "\n1)Yes" << endl;
    cout << "2) No" << endl;
    cin >> choice;

    if(choice == 1)
    {
        cout << "Thank you " << accountHolderName << " your account has now been created! Don't forget your pin or you wont be able to login." << endl;
    }
    else if(choice == 2)
    {
        cout << "We're sorry we could not create your account at this time" << endl;
    }
}

void Bank::MainMenu()
{
    int choice = 0;

    do
    {
        cout << "Would you like to create an account or access an existing account?\n" << endl;

        cout << "1) Create a new account." << endl;
        cout << "2) Access an existing account.\n" << endl;

        cin >> choice;

        if(choice == 1)
        {
            CreateNewAccount();
        }
        else if(choice == 2)
        {
            AccountInformation();
        }
    }while(choice != -1);
}

int main()
{
    Bank bank;

    bank.MainMenu();

    return 0;
}
Last edited on
I would say the logic could be broken up. Your class does the job of both the bank and the bank account.

You could have a class to handle just the Bank logic and a class to handle the BankAccount logic. Then your Bank can store accounts and act as an intermediary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BankAccount
{
public:
  BankAccount() = default;
  ~BankAccount() = default;

  int GetBalance();
  bool Withdraw(int Amount);
  void Desposit(int Amount);
  bool CheckCredentials(const std::string &NameEntered, int PinEntered);

private:

  std::string ownerName = "Default";
  int accountNumber = 0;
  int currentBalance = 0;
  int accountPin = 0;
  int holderAge = 0;

};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Bank
{
public:
  Bank() = default;
  ~Bank() = default;

  void MainMenu();

private:

  bool CreateNewAccount();
  bool AccessAccount();

  //And some variable to store the accounts
  std::map<int, BankAccount> accounts;

};


That examples ignores all security best-practices, but it's illustrative.


However, this is now a good chance for you to use a constructor to set some value for the BankAccount's data. If you want some piece of data to be immutable (the account number, for example), you could pass the value entered by the user to the constructor and make the BankAccount's member variable for it be const-qualified so it can never be changed.

Like
1
2
3
4
5
6
7
8
bool Bank::CreateAccount()
{
  int acctNumber = 0;
  //generate some account number
  //get info from user, etc
  accounts[acctNumber] = BankAccount(NameEntered, PinEntered, acctNumber);
  ...
}

Then BankAccount would have a constructor that takes a string and two ints that sets its members appropriately.

I try to make my code do what i want in as little code as possible if i know how/spot the problem.

That's generally a good idea. However, shorter code isn't always easier to read, and making it a little longer can make it more readable.
Take, for example:

This piece of short code:
1
2
3
string slurp(ifstream& in) {
    return static_cast<stringstream const&>(stringstream() << in.rdbuf()).str();
}


vs this longer piece:
1
2
3
4
5
string slurp(ifstream& in) {
    stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}


(from https://stackoverflow.com/questions/116038/what-is-the-best-way-to-read-an-entire-file-into-a-stdstring-in-c?noredirect=1&lq=1)

They both do the same thing, but I'd argue the latter is easier to look at and understand quickly.

Using it to initalize every single variable when it can just be done at its declaration point just seems like a waste of time and whitespace, but maybe thats just me.

Well, the less code clutter the better, and the more explicit the better; the two will make it easier for other to understand your code (or you yourself if you come back to it later and have forgotten its purpose/design).



Polymorphism is where I think C++ truly shines. Plenty of languages have class inheritance, but I've yet to encounter any that really leverage it like C++ does.
Ok, I think I fully understand classes now. Not too difficult. I just need to break my logic down better like you said. Now, onto the next thing. Inheritance. Here is some basic inheritance code I wrote. I believe I created the base and sub classes correctly, but im not sure if im inheriting functions correctly, also I cant seem to figure out what else to put in the functions, as all the code that would be relevant to a vehicle is in the base class. There might be some things im missing but idk if it would warrant making all those classes. I dont want to use any virtual yet, that will just confuse things for me, once i get inheritance i'll move to virtual.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>

using namespace std;

class Vehicle
{
    public:
        Vehicle() = default;
        ~Vehicle() = default;

        void Steer();
        void Decelerate();
        void Accelerate();
        void Refuel();

    private:
        float turnAmount = 0;
        float decelerateRate = 0;
        float accelerateRate = 0;
        float fuelCapacity = 3;
        float windDragAmount = 0;
};

class Automobile: public Vehicle
{
    public:
        void Steer();
        void Decelerate();
        void Accelerate();
        void Refuel();

};

class DodgeViper: public Automobile
{
    public:
        void Steer();
        void Decelerate();
        void Accelerate();
        void Refuel();
};


class Aircraft: public Vehicle
{
    public:
        void Steer();
        void Decelerate();
        void Accelerate();
        void Refuel();
};

class Cessna172: public Aircraft
{
    public:
        void Steer();
        void Decelerate();
        void Accelerate();
        void Refuel();
};


class Locomotive: public Vehicle
{

};

int main()
{
    
    return 0;
}
Digression in http://www.gotw.ca/publications/mill08.htm mentions "name hiding". Does your example have that issue?
no i dont believe so. As far as i know anyways.
Last edited on
Given your class definitions, are these ok and what they do:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
    Locomotive loco;
    loco.Steer();
    Vehicle* ploc = &loco;
    ploc->Steer();

    Cessna172 cess;
    cess.Steer();
    Aircraft* aces = &cess;
    aces->Steer();
    Vehicle* vces = &cess;
    vces->Steer();

    return 0;
}
Last edited on
Topic archived. No new replies allowed.