-2
\$\begingroup\$

In my project I need to have one class as singleton which must be globally accessible:

board.h:

#pragma once

#include "io/io.h"
#include "lib/drv/gpio.hpp"

class Board {
public:
    static const unsigned int CORE_FREQ = 48000000;
    static const unsigned int SYSTICK_FREQ = 1000;

    volatile uint64_t tickCounter;

    GpioPin<io::base::GPIOA, 8> mco;

    GpioPin<io::base::GPIOA, 0> button;

    GpioPin<io::base::GPIOC, 9> ledGreen;
    GpioPin<io::base::GPIOC, 8> ledBlue;

    Board();

    /* Return clock ticks since restart
    * @return number of ticks since restart */
    uint64_t getTicks();

    static Board board;
};

board.cpp:

#include "board.hpp"

Board::Board() {
    // Enable HSI oscillator 8MHz
    io::rcc::r.CR.b.HSION = true;
    while (!io::rcc::r.CR.b.HSIRDY);
    io::rcc::r.CFGR.b.SW = (int)io::rcc::Cfgr::Sw::HSI;

    // Set prescalers for AHB and APB
    io::rcc::r.CFGR.b.HPRE = (int)io::rcc::Cfgr::Hpre::DIV_1;
    io::rcc::r.CFGR.b.PPRE = (int)io::rcc::Cfgr::Ppre::DIV_1;

    // set PLL to 48MHz from HSI clock
    io::rcc::r.CFGR.b.PLLSRC = (int)io::rcc::Cfgr::Pllsrc::HSI_DIV_2;
    io::rcc::r.CFGR.b.PLLMUL = (int)io::rcc::Cfgr::Pllmul::MUL_12;
    io::rcc::r.CR.b.PLLON = true;
    while (!io::rcc::r.CR.b.PLLRDY);
    io::rcc::r.CFGR.b.SW = (int)io::rcc::Cfgr::Sw::PLL;

    // Send HCI to MCO output
    io::rcc::r.CFGR.b.MCOPRE = (int)io::rcc::Cfgr::Mcopre::DIV_1;
    io::rcc::r.CFGR.b.MCO = (int)io::rcc::Cfgr::Mco::HSI;

    // NVIC
    io::nvic::isrEnable();

    // SYSTICK
    io::systick::r.configure(CORE_FREQ / SYSTICK_FREQ);
    tickCounter = 0;

    // CLOCK ENABLE
    io::rcc::r.AHBENR.b.GPIOA = true;
    io::rcc::r.AHBENR.b.GPIOC = true;

    // GPIO
    mco.setAf(0).setOtype(io::gpio::Otype::PUSH_PULL).setOspeed(io::gpio::Ospeed::HIGH);

    // Configure button
    button.setInput();

    // Configure LEDs
    ledGreen.setOutput().setOtype(io::gpio::Otype::PUSH_PULL);
    ledBlue.setOutput().setOtype(io::gpio::Otype::PUSH_PULL);
}

unsigned long long Board::getTicks() {
    unsigned long long major;
    unsigned int minor;
    do {
        major = tickCounter;
        minor = io::systick::r.VAL.CURRENT;
    } while (major != tickCounter);
    major += 1;
    major *= CORE_FREQ / SYSTICK_FREQ;
    return major - minor;
}

void HARDFAULT_handler() {
    while (true) {
        Board::board.ledBlue.toggle();
    }
}

void SYSTICK_handler() {
    Board::board.tickCounter++;
}

Board Board::board;

main.cpp:

#include "board.hpp"

class MainClass {
private:
    unsigned int blinkTicks;
    uint64_t ticks;

    void process(unsigned int delta) {
        blinkTicks += delta;
        if (blinkTicks > 4800000) {
            blinkTicks = 0;
            Board::board.ledGreen.toggle();
        }
    }

public:
    MainClass() {
        blinkTicks = 0;
        ticks = Board::board.getTicks();
    }

    void run() {
        Board::board.ledBlue.set();
        while (true) {
            uint64_t tm = ticks;
            ticks = Board::board.getTicks();
            tm = ticks - tm;
            if (tm > 0) process(tm);
        }
    }
};

MainClass mainClass;

void mainApp() {
    mainClass.run();
}

(This class define some board specific configurations and definitions for microcontroller.)

In my case singleton is necessary, because it is called from HW interrupts handlers.

Is it good practice? How can I do it better?

\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$
  1. The Singleton pattern isn't very much recommended to begin with so are you sure you really need it / want to use it? If you haven't been doused with loads of opinions about this topic yet, DuckDuckGo for it now.

  2. If you really want only a single instance of your class to ever exist, you should make all constructors private and delete the copy / move constructors and assignment operators.

  3. Another problem with your implementation is that you will always construct the static board member even if an instance of the Board class is never requested. If you were to allocate the object on the free store, you could do it lazily but that would require you to do a little more work and considerably more work if you want it to be thread-safe. (And we've learned that we should only use the free store as a last resort anyway.) So the recommended way for implementing the Singleton pattern in modern C++ is to use a local static object that the language guarantees will be constructed lazily in a thread-safe manner.

    Board&
    Board::getInstance()
    {
      static Board instance {};
      return instance;
    }
    

    I've heard this to be referred to as “Meyers' Singleton” presumably because Scott Meyers was the first to suggest it (or at least made it popular) but I can't point you to any reliable source for this.

\$\endgroup\$
5
  • \$\begingroup\$ I also don't like singletons or other globals, but In this case for whole project is necessary to have only one - need give access to this class from interrupt handlers, which are just static functions. \$\endgroup\$ Commented Sep 18, 2015 at 20:23
  • \$\begingroup\$ You can still have a single static instance of a non-Singleton class. Think of std::cout, for example. \$\endgroup\$ Commented Sep 18, 2015 at 20:25
  • \$\begingroup\$ Check to make sure that any parts of your answer haven't been invalidated by the question edit. If so, something will have to be reversed. \$\endgroup\$ Commented Sep 18, 2015 at 20:49
  • \$\begingroup\$ @Jamal I couldn't find anything. I don't feel competent to review the io::rcc::r.CFGR.b.PLLSRC stuff but as far as I can see it doesn't affect any of the points about the Singleton pattern (and was not asked for by the OP). Did you spot an inconsistency? \$\endgroup\$ Commented Sep 18, 2015 at 20:57
  • 1
    \$\begingroup\$ I'm just asking you to make sure, since it's your answer. Answer invalidations can sometimes be hard to determine. \$\endgroup\$ Commented Sep 18, 2015 at 21:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.