References

Pass By Copy/Reference

  • Computer literature has terminology for different ways of parameter passing

  • Terms: “Pass by copy”, “pass by reference”

  • Terms: “input parameter”, “output parameter”, “input/output parameter”

  • Pass by copy: callee receives a copy of caller’s value (⟶ function cannot modify caller’s value)

  • Pass by reference: callee receives a reference to caller’s value (⟶ callee can modify caller’s value)

This terminology is language independent (Pascal?)

Pointers Can Be Left Uninitialized

Pointer are evil: can be left uninitilized …

int main()
{
    int* pi;
    *pi = 666; //   <--- EVIL!
    return 0;
}
$ ./code/c++03-references-uninitialized-pointer
Segmentation fault (core dumped)

References Cannot (Easily) Be Left Uninitialized

C++ does not let you leave references uninitialized:

int main()
{
    int& i;
    i = 666; //   <--- only potentially evil
    return 0;
}
code/c++03-references-uninitialized-reference.cpp: In function ‘int main()’:
code/c++03-references-uninitialized-reference.cpp:3:10: error: ‘i’ declared as reference but not initialized
    3 |     int& i;
      |          ^

Copy? Reference? C? Pointers!

  • In C, parameters are passed by copy only

    #include <gtest/gtest.h>
    
    void pass_by_copy(int j)
    {
        j = 666;
    }
    
    TEST(references_suite, copy)
    {
        int i = 42;
        pass_by_copy(i);
        ASSERT_EQ(i, 42);
    }
    
  • But: there are addresses (pointers) that can be passed (by copy 🤨)

    • Voila, done!

    • As long as one can handle those & and *, everything is ok

    • A little tedious though: take address (&), dereference address (*)

    • ⟶ error prone

    #include <gtest/gtest.h>
    
    void pass_by_pointer(int* j)
    {
        *j = 666;              // <--- dereference j explicitly 
    }
    
    TEST(references_suite, pointer)
    {
        int i = 42;
        pass_by_pointer(&i);   // <--- take address of i explicitly
        ASSERT_EQ(i, 666);
    }
    

C++: True References

  • C++ adds another meaning to the & operator: reference

  • Used in function declaration only

  • Every use looks like regular variable/parameter access

  • Internally addresses are taken, just like pointers - only easier

  • Advantage: references cannot (easily) dangle, or be NULL

#include <gtest/gtest.h>

void pass_by_reference(int& j)
{
    j = 666;                 // <--- dereference j implicitly
}

TEST(references_suite, reference)
{
    int i = 42;
    pass_by_reference(i);    // <--- take address of i implicitly
    ASSERT_EQ(i, 666);
}

And const? Pointers?

  • In C, there is the const keyword which can be applied to pointers too

  • Meaning: “this is the address of something that must not be modified”

#include <gtest/gtest.h>

void pass_by_const_pointer(const int* j)
{
    *j = 666;              // <--- error: *j is const
}

TEST(references_suite, const_pointer)
{
    int i = 42;
    pass_by_const_pointer(&i);   // <--- pass address to *const* i
}
code/pass-by-const-pointer.cpp:5:8: error: assignment of read-only location ‘* j’
    5 |     *j = 666;              // <--- error: *j is const
      |     ~~~^~~~~

const References

#include <gtest/gtest.h>

void pass_by_const_reference(const int& j)
{
    j = 666;                 // <--- error: j is const
}

TEST(references_suite, const_reference)
{
    int i = 42;
    pass_by_const_reference(i);    // <--- pass reference to const i
    ASSERT_EQ(i, 666);
}
code/pass-by-const-reference.cpp:5:7: error: assignment of read-only reference ‘j’
    5 |     j = 666;                 // <--- error: j is const
      |     ~~^~~~~

const Reference: Substitute For “Pass by Copy”

  • Pass by copy protects from accidental modification

  • Can be expensive though

    #include <gtest/gtest.h>
    
    struct person
    {
        char firstname[128];
        char lastname[128];
    };
    
    void pass_by_copy_expensive(person p)
    {
        // ... do something with p, read-only ...
    }
    
    TEST(references_suite, copy_expensive)
    {
        person p{"Joerg", "Faschingbauer"};
        pass_by_copy_expensive(p);   // <--- copying 256 bytes to callee
    }
    
  • const references to the rescue

  • Amount of bytes copied is the size of a pointer, no matter what the target type is

    #include <gtest/gtest.h>
    
    struct person
    {
        char firstname[128];
        char lastname[128];
    };
    
    void pass_by_const_reference_cheap(const person& p)
    {
        // ... do something with &p, read-only ...
    }
    
    TEST(references_suite, const_reference_cheap)
    {
        person p{"Joerg", "Faschingbauer"};
        pass_by_const_reference_cheap(p);   // <--- copying 8 bytes (a pointer) to callee
    }