phibao37
phibao37

Reputation: 2350

C++ public method inherited from base class can not access private member variable in derived class

I have the code:

#include <iostream>
#include <cstdlib>
using namespace std;

class Animal{
private:
    int age;
public:
    Animal() : age(1) {}
    void toString(){
        cout << "Age: " << age << endl;
    }
};
class Cat : public Animal
{
public:
    Cat() : age(5) {}
    /*
    void toString(){
        cout << "Age: " << age << endl;
    }*/
private:
    int age;
};

int main(){
    Cat tom;
    tom.toString();

    system("pause");
    return 0;
}

But when I run the program, the age of the tom variable is 1, not 5. Does the toString can not read the age variable? If we open the /* */ the toString method in the Cat class, the age will be 5 !

(My english is not good very much. Thanks)

Upvotes: 3

Views: 2666

Answers (3)

Loki Astari
Loki Astari

Reputation: 264381

Try:

#include <iostream>
#include <cstdlib>
using namespace std;

class Animal{
private:
    int age;
public:
    Animal(int a = 1)   // Pass in the age as a parameter.
       : age(a)         // Default to 1.
    {}

    // Prefer generic print function rather than toString()
    friend std::ostream& operator<<(std::ostream& s, Animal const& a) {
        return s << "Age: " << a.age << '\n';  // Prefer '\n' rather than endl
                                               // Unless you really want to flush
                                               // the stream (this is not usually
                                               // the case).
    }
};
class Cat : public Animal
{
public:
    Cat()
       : Animal(5)      // Now you can call the base class constructor
    {}                  // And get it to set 5
private:
    // int age;         // don't have a private copy here.
                        // use the one that is available in the base class.


    // Prefer generic print function rather than toString()
    friend std::ostream& operator<<(std::ostream& s, Cat const& a)
    {
        // Print Cat
        // Then use the Animal priting function to print more information about the object.
        return s << "A Cat: " << static_cast<Animal const&>(*a);
    }
};

int main(){
    Cat tom;

    // tom.toString(); // Don't use a toString() method.
                       // overload operator<< to print to a stream.
                       // If you want to convert to a string the just print
                       // to a string stream.

    std::cout << tom;

    system("pause");
    return 0;
}

Upvotes: 0

Mark Garcia
Mark Garcia

Reputation: 17708

The problem is that Cat is writing to the age variable in Cat, while toString() reads the age variable in Animal, which, with Animal's constructor, is initialized to 1.

To solve this, you can provide another constructor for Animal which accepts an age parameter which is used to initialize Animal's age member variable.

class Animal{
private:
    int age;
public:
    Animal() : age(1) {}
    Animal(int param_age) : age(param_age) {} // Initialize member variable age with parameter
    void toString(){
        cout << "Age: " << age << endl;
    }
};

class Cat : public Animal
{
public:
    Cat() : Animal(5) {} // Call Animal's constructor that set's the age
};

UPDATE: Another solution is to add a setter method in Animal class that sets its age. You can then call it in Cat's constructor to set the proper age.

class Animal{
private:
    int age;
public:
    Animal() : age(1) {}
    void setAge(int age) { this->age = age; }
    void toString(){
        cout << "Age: " << age << endl;
    }
};
class Cat : public Animal
{
public:
    Cat() {
        setAge(5);
    }
};

Yet another alternative is to make Animal's age member protected

class Animal{
protected:  // THIS
    int age;
public:
    Animal() : age(1) {}
    void toString(){
        cout << "Age: " << age << endl;
    }
};

And remove Cat's age variable in the class definition. Despite its simplicity, this approach gives you more risk in encountering the "brittle base class" problem. Thus, I recommend the former solution as it is less prone to the said problem, and IMHO better sticks to the "write against interfaces, not implementations" principle.

Upvotes: 2

Paul Draper
Paul Draper

Reputation: 83225

The problem is that you are setting Cat::age in the Cat constructor, not the Animal::age used by Animal::toString.

Change the visibility of Animal::age to protected.

class Animal {
protected:
    int age;
public:
    Animal() : age(1) {}
    void toString(){
        cout << "Age: " << age << endl;
    }
};

Don't redeclare a second age (which becomes Cat::age). Instead, change the value of age (Animal::age).

class Cat : public Animal {
public:
    Cat() {
        age = 5;
    }
};

Upvotes: 1

Related Questions