It's unfortunate that we can't fix the provided code, as there is a lot more wrong with it than with your code, in my view. Because this is a learning exercise and because it may benefit others who read this later, I'm going to review both pieces of code, starting with the code you actually wrote.
Fix the bug
There is one instance in which endl
is used without std::
namespace. It won't compile that way on my machine. In any case, see the next suggestion.
Don't use std::endl
if you don't really need it
The difference betweeen std::endl
and '\n'
is that '\n'
just emits a newline character, while std::endl
actually flushes the stream. This can be time-consuming in a program with a lot of I/O and is rarely actually needed. It's best to only use std::endl
when you have some good reason to flush the stream and it's not very often needed for simple programs such as this one. Avoiding the habit of using std::endl
when '\n'
will do will pay dividends in the future as you write more complex programs with more I/O and where performance needs to be maximized.
Understand inline
The inline
specifier is new with C++17 and would allow one to, in this case, put the initialization of the member variable id_count
within the header file, even if the header file is #include
d in multiple other files (technically translation units). In the context of the original code, there's not much real benefit to it, since there appears to be only one translation unit. Using C++11 (or C++14) syntax, one could simply omit the inline
keyword and then write
int Student::id_count{0};
outside the class definition. Maybe you already knew all of that, but I think it's worthwhile clarifying what it means and how it works.
Prefer modern initializers for constructors
The constructor could use the more modern initializer style rather than the old style you're currently using. Instead of this:
Student(){
cur_id = ++Student::id_count;
}
You could use this:
Student() :
cur_id{++id_count}
{}
There is not a significant difference in this code, but it's a good habit to get into using.
Avoid using explicit this
The current code includes this:
std::cin >> this->name;
std::cin >> this->age;
There's really no compelling reason to have this->
here explicitly and no good reason not to chain the two extractions. I'd write it like this:
std::cin >> name >> age;
Think carefully about common code
Both the Student
and Professor
classes read in and write out the name and age first. Since that's common to both, I'd be inclined to put that into the base Person
class. As an example, within the Person
class:
virtual void getdata() {
std::cin >> name >> age;
}
Then within the Student
class:
void getdata() override {
Person::getdata(); // call the base class
for (auto &mark : marks) { // do things unique to Student
std::cin >> mark;
}
}
This would also have the benefit that you could have Person
member data private
instead of protected
.
Consider explicitly defining all member data
What is the value of name
just after a Professor
object is created? If you don't know, or if you know but think it ould be improved, I'd suggest explicitly defining all member data such that a constructed object has a known, rational state.
Make your base class destructor virtual
By default, a compiler-generated destructor is concrete rather than virtual, but this leads to problems with collections of objects. See this question for more details on why.
Consider the user
It would be convenient to be able to specify streams other than std::cin
and std::cout
for your classes. One way to do that and still be able to use the given inteface code would be to have a default parameter like this:
void putdata(std::ostream &out = std::cout) const override;
That way, calling code that does not specify an argument will get std::cout
but it's still possible to explicitly specify some other stream.
Comments on given code harness
Match new
with delete
If you allocate memory using new
, you should also free it using delete
or your program will leak memory. Since main
uses new
to create each Person
object, it should also delete
each such object. Yes, the operating system will generally clean up such messes, but there's little advantage in creating them in the first place.
Use more descriptive variable names
Single-letter variable names and extremely generic names like val
make it harder to understand the program. More meaningful variable names make the code easier to read and maintain.
Check your if
statements for proper braces
It's not helpful to use curly braces on the if
clause and then omit them on the else
clause. Generally, I prefer to always have the braces. It makes the intent clear and prevents bugs being introduced in maintenance.
“”
/ block quote button" (or its keyboard short-cut) in the post editor or put>
before each line (works with nested contents like code-blocks, too). \$\endgroup\$n, val, per
. Leaks memory without even a comment about it. Bad naming of the virtual functionsgetdata()
andputdata()
. When we seegetfoo
, we expect that it is a getter function, and doesn't modify state, andputfoo
is expected to be some kind of setter function (setfoo
is a more common name), which does modify state; instead, they do the opposite. \$\endgroup\$