#098 – C++ offers a 3rd way to pass in objects

Everyone is familiar with pass by value and pass by reference. However, C++ has a third way of passing in objects. This is called pass by address.

How pass by address is performed.

Instead of passing in a lvalue reference, expression or rvalue, the caller passes in the object’s address.

When the function call executes, &str creates a pointer to this address. This pointer is copied to the function parameter.

The function parameter is a pointer.

The function can dereference it to get the actual object at that address and manipulate it however it likes.

#include <iostream>
#include <string>

void printLabel(const std::string* label){
    std::cout << *label << '\\n';
}

int main(){
    std::string deviceLabel { "Pump Controller A" };

    printLabel(&deviceLabel);
}

// Output:
Pump Controller A

Pass by address is beneficial since it doesn’t make a duplicate of the argument. This makes sense since you’re passing in an address.

The example above uses a pointer to const. This ensures the function cannot modify the argument. const can be dropped if you desire the function to modify the argument. E.g.

#include <iostream>

struct MotorConfig {
    int speedRpm {};
    int currentLimitmA {};
};

// non-const pointer
void applySafeDefaults(MotorConfig* config){
    config->speedRpm = 1500;
    config->currentLimitmA = 800;
}

int main(){
    MotorConfig config {
        .speedRpm = 0,
        .currentLimitmA = 0
    };

    applySafeDefaults(&config);

    std::cout << "Speed: " << config.speedRpm << " rpm\\n";
    std::cout << "Current limit: " << config.currentLimitmA << " mA\\n";
}

// Output:
Speed: 1500 rpm
Current limit: 800 mA

Null checking

Pointers can be null. This is the main difference between pointers and references. This means we have to be more cautious.

The example below prevents dereferencing null pointers

#include <iostream>

void printReading(const int* reading)
{
    if (!reading) {
        std::cout << "No reading available\\n";
        return;
    }

    std::cout << "Reading: " << *reading << '\\n';
}

int main()
{
    int adcValue { 812 };

    printReading(&adcValue);
    printReading(nullptr);
}

// Output:
Reading: 812
No reading available

An assert can also be used if a nullptr should not ever be present.

assert(reading && "reading must not be null");

Despite everything I said, prefer pass by (const) reference

  1. nly lvalues can be used as arguments. E.g. doSomething(&11) isn’t allowed since rvalues do not have addresses. This limits the useability of pass by address
  2. Litters our code with pointer semantics, i.e. &, *, ->.
  3. Avoids risk of dereferencing a nullptr.