2

I'm designing some classes to access and control the peripherals of a microcontroller (adc, port, usart etc). The device have just a few (in some cases just one) instances of each peripheral, so I decided to represent each peripheral as a monostate class. The definition and usage of one of my classes would be something like this:

usart.h

class usart {
public:
    static void init() { /* initialize the peripheral */ } 
    static char read() { /* read a char from the input buffer */ }
    static void write(char ch) { /* write a char to the output buffer */ }
    // ... more member functions
};

main1.cpp

#include "usart.h"

int main()
{
    usart::init();

    char data;
    while (true) {
        data = usart::read();
        usart::write(data);
    }

}

But the way the usart class is defined above doesn't forbid the user from doing something like this:

main2.cpp

#include "usart.h"

int main() 
{
    // I don't want object construction
    usart serial1;
    usart serial2;

    // neither assignment
    serial1 = serial2;

    // two objects representing the same hardware resource
    // I don't want that
    serial1.init();
    serial2.write('r');
}

I know since C++11 I can use the delete keyword to prevent the compiler of creating default constructors and functions, but I don't know exactly what are those defaults the compiler creates. There are copy constructors, copy assigments, move semantics overloads etc. How many deletes I need to put on my class (and in what functions and constructors)?

update: I know I could (and maybe should) use namespaces instead of classes, but I'm afraid later I'll need to pass those classes (or namespaces) as template arguments. AFAIK it is not possible to use namespaces as template arguments, because of that I chose to use classes with static members instead of namespaces.

  • 2
    `~usart() = delete;` Now no instance can be created. – Igor Tandetnik Jul 20 '17 at 13:32
  • afaik If you provide only one of those the compiler wont create any of them – 463035818_is_not_an_ai Jul 20 '17 at 13:33
  • 7
    if you plan to have no instances - why to use a class? just use a namespace with functions. – k.v. Jul 20 '17 at 13:33
  • make a `private` contructor – ikleschenkov Jul 20 '17 at 13:34
  • btw why do you spend the effort to have a monostate? if at some point "in some cases just one" becomes "in some cases more than one" you will need several instances anyhow – 463035818_is_not_an_ai Jul 20 '17 at 13:35
  • @k.v. What if later I need to use one of those classes/namespaces as a template argument? I can do do that only with classes, am I right? –  Jul 20 '17 at 13:39
  • 2
    Simple, don't abuse classes. `namespace usart { }`. – StoryTeller - Unslander Monica Jul 20 '17 at 13:42
  • @tobi303 Even in the "in some cases more than one" scenario I will have 2 or 3 hardware instances. But on the microcontroller I'm working now, there's just one instance of each one of that peripherals. –  Jul 20 '17 at 13:43
  • @IgorTandetnik The compiler will not create any default if I use `~usart() = delete`? –  Jul 20 '17 at 13:45
  • Will not create any default what? – Igor Tandetnik Jul 20 '17 at 13:49
  • 1
    yagni. I have dealt with several kinds of 'singleton' hw, (fpga's, cpld's, usart, etc.) IMHO, there is no effort you can expend now that won't prevent 'errors' by someone unfamiliar with your code and system in the future. Any error they do is not your fault. Make your use of class simple, direct, readable. In my embedded software efforts, static methods attributes are rare to non-existent, and static data attributes are limited to 'unique-id' counters. I repeat, yagni. – 2785528 Jul 20 '17 at 13:50
  • @IgorTandetnik Any default constructor, copy constructor, copy assignment etc. –  Jul 20 '17 at 13:51
  • 1
    Actually, I take it back. Just disabling a destructor still lets one write `usart* p = new usart();` (and then leak the instance, as they would be unable to destroy it). Disabling a default constructor (and not providing any others), however, would do the trick: `usart() = delete;` Yes, the compiler will generate copy constructors and such - but before you can copy an instance, you need to create an instance to copy *from*, and there won't be a constructor for that. – Igor Tandetnik Jul 20 '17 at 13:56

2 Answers2

4
struct cannot_exist {
  cannot_exist()=delete;
  ~cannot_exist()=delete;
  cannot_exist(cannot_exist const&)=delete;
  cannot_exist(cannot_exist &&)=delete;
  cannot_exist& operator=(cannot_exist const&)=delete;
  cannot_exist& operator=(cannot_exist &&)=delete;
};

this is a class with every member that C++ generates for you deleted explicitly. (You can do this with fewer, but I don't see the point being less explicit).

Simply inherit from cannot_exist and ... an instance of your class cannot exist, nor will it have any member function auto defined by the compiler. Attempts to call them will generate compiler errors.

However, once you have a class that cannot exist, consider using a namespace.

class usart:cannot_exist {
public:
  static void init() { /* initialize the peripheral */ } 
  static char read() { /* read a char from the input buffer */ }
  static void write(char ch) { /* write a char to the output buffer */ }
  // ... more member functions
};

vs

namespace usart {
  static void init() { /* initialize the peripheral */ } 
  static char read() { /* read a char from the input buffer */ }
  static void write(char ch) { /* write a char to the output buffer */ }
  // ... more member functions
};
Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

I would try these two ways

  1. Declare a pure virtual function in the class. This makes the class pure virtual so it cannot be instantiated.

  2. Declare the default constructor as a private member function. This way no outside class method can access the constructor.

AdityaG
  • 428
  • 1
  • 3
  • 17