Search code examples
c++classobjectpass-by-value

C++: Passing objects by value to a member function of the same class


I'm a beginner in C++ and I've just started learning about OOP. In the following program I've added objects of the same classes and displayed the result. However, I'm not able to understand the fact that if I pass the objects to the function by value then how is the change reflected in the calling function. The addNumbers() function expects two objects of the class Complex and the object which is used to invoke the function (c3.addNumbers(c1, c2)) is implicitly passed to the function but how are the values of c3.real and c3.imaginary affected in the calling function since addNumbers() has no access to their "location" in the memory. Any help will be appreciated!

Thanks in advance!

class complex {
private:
    int real;
    int imaginary;

public:
/* Using member initializers to assign values to members */    
    complex()
        : real(0)
        , imaginary(0)
    {}

    void readData(int x, int y);

    void printData();

    void addNumbers(complex, complex);
};     

void complex::readData(int x, int y)
{
    real      = x;
    imaginary = y;
}

void complex::printData()
{
    cout << real << "+" << imaginary << "i" << endl;
}   

void complex::addNumbers(complex c1, complex c2)
{
    real      = c1.real + c2.real;
    imaginary = c1.imaginary + c2.imaginary;
}

int main(void)
{
    complex c1, c2, c3;
    c1.readData(-5,17);
    c2.readData(11,7);
    c3.addNumbers(c1,c2);
    c3.printData();

    return 0;
}

Solution

  • I made a few comments in your original code to explain why real and imaginary are affect below. (Look for //MABVT)

    In addition: I will provide another useful example for you to progress further!

    REVIEW

    class complex {
    private:
        int real;
        int imaginary;
    
    public:
        /* Using member initializers to assign values to members */    
        complex()
            : real(0)
            , imaginary(0)
        {}
    
        void readData(int x, int y);
    
        void printData();
    
        // MABVT: You provide two complex numbers which you want to add 
        //        together!
        void addNumbers(complex, complex);
    };     
    
    void complex::readData(int x, int y)
    {
        real      = x;
        imaginary = y;
    }
    
    void complex::printData()
    {
        cout << real << "+" << imaginary << "i" << endl;
    }   
    
    void complex::addNumbers(complex c1, complex c2)
    {
        // MABVT: Use c1.component and c2.component, add them up and store them 
        //        in this class' instance.
        real      = c1.real      + c2.real;
        imaginary = c1.imaginary + c2.imaginary;
    
        // MABVT: c3.real and c3.imaginary are affected at this exact location
        //        since you overwrite the values with the addition-results.
        //        Since the function addNumbers(complex, complex) is invoked
        //        on the complex instance 'c3', real and imaginary of c3 are 
        //        known in this context, and consequently you can use them.
        //
        //        To attach to your statement that the c3 instance's pointer is 
        //        implicitly passed: 
        //        Yes it is passed as the first parameter invisibly as 
        //         'complex* this'
        //
        //        So you could also write:
        //          this->real = c1.real + c2.real; (see the use of this?)
    }
    
    int main(void)
    {
        complex c1, c2, c3;
        c1.readData(-5,17);
        c2.readData(11,7);
        c3.addNumbers(c1,c2);
        c3.printData();
    
        return 0;
    }
    

    ALTERNATIVE

    // Example program
    #include <iostream>
    #include <string>
    
    class Complex { // Give class names capital first letter
    private:
        int m_real;      // Just a recommendation: I'd like to be able to distinguish parameter for member in the identifier already!
        int m_imaginary; // Just a recommendation: I'd like to be able to distinguish parameter for member in the identifier already!
    
    public:
        /* Using member initializers to assign values to members */    
        inline Complex()   // Inline it, if you define this class in a header and reuse it multiple times...
            : m_real(0)
            , m_imaginary(0)
        {}
    
        // Provide initializing constructor to be able to construct 
        // a complex number quickly. Replaces your readData(...);
        inline Complex(
            int inRealPart,
            int inImaginaryPart)
            : m_real(inRealPart)
            , m_imaginary(inImaginaryPart)
        {}
    
        // Getters to read the values
        inline int real()      const { return m_real; }
        inline int imaginary() const { return m_imaginary; }
    
        void printData();
    
        // Local assignment-add operator to add another complex
        // to this specific instance of complex and modify the internal
        // values. Basically what you did as the second part of addNumbers.
        Complex& operator+=(const Complex& r);
    };     
    
    void Complex::printData()
    {
        std::cout << m_real << "+" << m_imaginary << "i" << std::endl;
    }   
    
    // Member add-assign operator definition adding this instance and another instance 'r' by adding up the values and storing them in the instance this operator is called on.
    Complex& Complex::operator +=(const Complex& r) 
    { 
        std::cout << "Local" << std::endl;
    
        this->m_real      += r.real();
        this->m_imaginary += r.imaginary();
    
        return *this;
    }
    
    // Static global operator+ definition, taking two values and creating a 
    // third, NEW one initialized with the results.
    // This was the first part of addNumbers
    static Complex operator+(const Complex& l, const Complex& r) { 
       std::cout << "Static Global" << std::endl;
    
       return Complex(
                (l.real()      + r.real()), 
                (l.imaginary() + r.imaginary())
              );
    }
    
    int main(void)
    { 
        // Same as before
        Complex c1(-5, 17);
        Complex c2(11, 7);
        Complex c3(1, 2);
    
        // Test output
        c1.printData();
        c2.printData();
        c3.printData();
    
        std::cout << std::endl;
    
        Complex  c3 = (c1 + c2);           // Calls static global and c3 is overwritten with the result. Exactly like your addNumbers call
        c1 += c2;                          // instance local, will change c1's internal values ( see print out below )
        Complex  c5 = ::operator+(c1, c2); // Static global, c5 is initialized with the result. Exactly like your addNumbers call
    
        std::cout << std::endl;
    
        c1.printData();
        c2.printData();
        c3.printData();
        c5.printData();
    
        return 0;
    }
    

    This should be quite much for you as a beginner.

    Some explanation

    Static global vs. local operator overloads

    Reading on the topic: http://en.cppreference.com/w/cpp/language/operators

    All the operators you use (+, -, *, /, %, +=, -=, ...) are just functions, which are predefined for primitive types and provided by libstd for STD types.

    You can override/define them though.

    I did that in two ways:

    Static global operator+:

    Accepts two arbitrary Complex instances and adds their components. Finally a NEW instance is created and initialized with the results.

    Basically this is just a static function, which is linked to "+" by the compiler.

    And:

    Local member operator+=:

    Accepts another instance of Complex and adds its component values to the component values of the instance the operator is called on: `l += r -> Called on l, whose values will be modified by adding the values of r'

    All op-assignment operators (+=, -=, *=, /=, etc...) must be defined within the class and can neither be global, nor static.

    const Type&

    Reading with much more info on const: https://www.cprogramming.com/tutorial/const_correctness.html

    Const reference to instances of whatever type will ensure two things for you:

    1. &: You only copy the address, but this way you function could change all public values or call most functions.
    2. const: The instance is not modifyable and nothing can be changed

    In combination that means: You don't have to copy the instance (pass-by-value), but provide it's address reference only (pass-by-reference). Usually that enhances performance, especially once you pass around large and complex objects.