C++ working with objects on heap

Viewed 1058

I am currently learning C++, coming from C#/Java background, using visual studio 2017.

I have a question in regards to creating objects on heap and referencing them properly down the road. So far I came across multiple tutorials and ways of doing things. Some recommend using smart pointers as much as possible, others swear its the devils tool.

My current main looks like this:

//Main
Person *makePerson()
{
    string name;
    int age;
    cout << "Input name: ";
    cin >> name;
    cout << "Input age: ";
    cin >> age;
    return new Person(name, age);
}

Child *makeChild(Person &parent)
{
    return new Child(*makePerson(), &parent);;
}

int main()
{
    cout << "---Input parent data---" << endl;
    Person *person = makePerson();
    cout << "printing: " << *person << endl;
    cout << "---Input child data---" << endl;
    Child *child = makeChild(*person);
    cout << "printing: " << *child << endl;
    cout << "---end of main---" << endl;
    delete person;
    delete child;
    return 0;
}

A function handles input of personal data and returns a pointer to new Person object. Then I have a function that handles creation of child object by taking a parent reference and asking makePerson for remaining data.

Can this be considered good C++? How can I make it better? I would really appreciate some code examples.

As some have already suggested, I could replace raw pointers with either shared_ptr<Person> person (heavy) or unique_ptr<Person> (better than shared).

This is code for Person and child classes. Note that Child has a raw pointer of type Person *parent.

//header
class Person
{
protected: 
    std::string name;
    int age;
public:
    Person();
    Person(const Person& other);
    Person(std::string inName, int inAge);
    ~Person();
    virtual void print() const;
    std::string getName() const;
    int getAge() const;
    Person &operator=(const Person &other);
    //overload print functionality, act as if it was toString
    friend std::ostream &operator<<(std::ostream &out, const Person &p);
};
//cpp
Person::Person() : name(""), age(0) {
    std::cout << "Person empty constructor" << std::endl;
}

Person::Person(std::string inName, int inAge) : name(inName), age(inAge) {
    std::cout << "Person (" << name << ") default constructor" << std::endl;
}

Person::Person(const Person & other) : name(other.name), age(other.age) {
    std::cout << "Person (" << name << ") copy constructor" << std::endl;
}

Person::~Person() {
    std::cout << "Person (" << name << ") destructor" << std::endl;
}

void Person::print() const {
    std::cout << name << ", " << age << std::endl;
}

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

int Person::getAge() const
{
    return age;
}

Person & Person::operator=(const Person & other) {
    std::cout << "Person (" << other.name << ") assignment constructor" << std::endl;
    name = other.name;
    age = other.age;
    return *this;
}
std::ostream &operator<<(std::ostream &out, const Person &p) {
    return out << p.name << ", " << p.age;
}

A child is a person and it makes sense for a child to have knowledge of who childs parent is. However, am uncertain how to handle this "knowledge". Here is code I am using for child class:

//Header
class Child : public Person
{
private:
    const Person *parent;
public:
    Child();
    Child(std::string name, int age);
    Child(std::string name, int age, const Person *parent);
    Child(const Child &child, const Person *parent);
    Child(const Person &person);
    ~Child();
    Child &operator=(const Child &other);
    void print() const;
    friend std::ostream &operator<<(std::ostream &out, const Child &c);
};
//cpp
Child::Child() {
    std::cout << "Child empty constructor" << std::endl;
}

Child::Child(std::string name, int age) : Person(name, age), parent(nullptr) {
    std::cout << "Orphan (" << name << ") constructor" << std::endl;
}

Child::Child(std::string name, int age, const Person *parent) :
    Person(name, age), parent(parent) {
    std::cout << "Child (" << name << ") default constructor" << std::endl;
}

Child::Child(const Child &child, const Person *parent) : 
    Person(child.name, child.age), parent(parent) {
    std::cout << "Child (" << child.name << ") copy constructor" << std::endl;
}

Child::Child(const Person &person) : Person(person), parent(nullptr) {
    std::cout << "Child from person (" << name << ") constructor" << std::endl;
}

Child::~Child() {
    std::cout << "Child (" << name << ") destructor" << std::endl;
}

Child & Child::operator=(const Child & other) {
    name = other.name;
    age = other.age;
    parent = other.parent;
    std::cout << "Child (" << name << ") assignment constructor" << std::endl;
    return *this;
}

void Child::print() const {
    if(parent)
        std::cout << *this << " is child of " << *parent << std::endl;
    else
        std::cout << *this << " is orphan" << std::endl;
}

std::ostream &operator<<(std::ostream &out, const Child &c) {
    return out << c.name << ", " << c.age << " is " << 
        (c.parent ? ("child of " + c.parent->getName() + ", " + std::to_string(c.parent->getAge())) : "orphan");
}

This is the output I get:

enter image description here

I suppose my question still remains, could anyone give me an example of how it should look like to be considered good C++?

@user4581301 if you take a look at the updated main, do you mean I should return a std::unique_ptrinstead of a * (raw pointer)? In which case my function will look like this:

std::unique_ptr<Person> makePerson2()
{
    string name;
    int age;
    cout << "Input name: ";
    cin >> name;
    cout << "Input age: ";
    cin >> age;
    return std::unique_ptr<Person>(new Person(name, age));
}

And variable declaration as:

std::unique_ptr<Person> upParent = makePerson2();
cout << "printing: " << *upParent << endl;

Would this be considered "better" C++ than what I have so far?

1 Answers
Related