Search code examples
c++armmicrocontrollerraiicortex-m

Is the object itself optimized out when I use this RAII-style pattern?


There is a RAII-style C++ pattern that implements scope-based ownership by creating a class that has no member and relying on the class's constructor and destructor (and the fact that the destructor is automatically called when the function returns). For example, the standard std::lock_guard implements this pattern.

I'm programming an EFM32 ARM Cortex-M microcontroller and came up with this class which uses a similar style:

#include <em_int.h>

class InterruptGuard final {

public:

    explicit inline InterruptGuard() {
        INT_Disable();
    }

    InterruptGuard(const InterruptGuard &other) = delete;

    InterruptGuard(const InterruptGuard &&other) = delete;

    inline ~InterruptGuard() {
        INT_Enable();
    }

    InterruptGuard &operator=(const InterruptGuard &other) = delete;

    InterruptGuard &operator=(const InterruptGuard &&other) = delete;

};

So if I want to disable interrupts inside a function with multiple return statements, I can make sure that they are going to be re-enabled without me worrying about explicitly re-enabling them at every return statement.

NOTE: the INT_Enable and INT_Disable functions implement a counter so INT_Enable will do the right thing and only enable interrupts when they really need to be enabled. So this class should be properly nestable.

void func() {
    InterruptGuard guard;

    // ...
}

My question is:

When I use this pattern, is the compiler going to do "the right thing" here and optimize out the object (so that no memory is actually consumed by this class) and just inline the INT_Enable and INT_Disable calls to the function that uses the InterruptGuard class?


Solution

  • Compiling with g++ -std=c++1y -O3 -Werror -Wextra (gcc version 5.3.0) this code:

    #include <cstdio>
    
    class InterruptGuard final {
    
    public:
    
        explicit inline InterruptGuard() {
            printf("enable\n");
        }
    
        InterruptGuard(const InterruptGuard &other) = delete;
    
        InterruptGuard(const InterruptGuard &&other) = delete;
    
        inline ~InterruptGuard() {
            printf("disable\n");
        }
    
        InterruptGuard &operator=(const InterruptGuard &other) = delete;
    
        InterruptGuard &operator=(const InterruptGuard &&other) = delete;
    
    };
    
    int main()
    {
        InterruptGuard i;
    }
    

    and this code:

    #include <cstdio>
    
    int main()
    {
      printf("enable\n");
      printf("disable\n");
    }
    

    gives the same assembly in both cases:

    .LC0:
            .string "enable"
    .LC1:
            .string "disable"
    main:
            subq    $8, %rsp
            movl    $.LC0, %edi
            call    puts
            movl    $.LC1, %edi
            call    puts
            xorl    %eax, %eax
            addq    $8, %rsp
            ret