patterncppModerate
Abstraction for an IO device
Viewed 0 times
abstractionfordevice
Problem
I have a C++ class which acts as an abstraction for an IO device which is controlled through file descriptors. As I am very new to C++, I would be glad if you can give me devastating feedback on things I've done wrong.
This especially covers:
gpio.h
gpio.cc
```
#include "../src/gpio.h"
#define GPIO_PATH_EXPORT "/sys/class/gpio/export"
#define GPIO_PATH_UNEXPORT "/sys/class/gpio/unexport"
#define GPIO_PATH_DIRECTORY "/sys/class/gpio/gpio%d"
#define GPIO_PATH_DIRECTION "/sys/class/gpio/gpio%d/direction"
#define GPIO_PATH_VALUE "/sys/class/gpio/gpio%d/value"
GPIO::GPIO(int id) {
Export();
OpenValueFd();
OpenDirectionFd();
}
GPIO::~GPIO() {
CloseValueFd();
CloseDirectionFd();
Unexport();
}
bool
GPIO::Exists() {
char * path;
if (asprintf(&path, GPIO_PATH_DIRECTORY, id) result++;
}
void
GPIO::Export() {
char * id;
if (Exists()) return;
if (asprintf(&id, "%d", _id) < 0)
throw "Error converting id to char.";
int fd = open(GPIO_PATH_EXPORT, O_WRONLY);
if (fd < 0)
throw "Error opening GPIO export.";
if (write(fd, id, strlen(id)) < 0)
This especially covers:
- Code style
- Design patterns
- Error handling
- Things which are open for failures
- C++ approaches which can be used over C approaches (file IO)
gpio.h
#ifndef SRC_GPIO_H_
#define SRC_GPIO_H_
#define GPIO_IN 0
#define GPIO_OUT 1
#define GPIO_LOW 0
#define GPIO_HIGH 1
class GPIO {
public:
explicit GPIO(int id);
~GPIO();
int Value();
void Value(int value);
int Direction();
void Direction(int value);
private:
int _id;
int _value_fd;
int _direction_fd;
bool Exists();
void Export();
void Unexport();
void OpenValueFd();
void OpenDirectionFd();
void CloseValueFd();
void CloseDirectionFd();
void SeekToTopOfValueFd();
void SeekToTopOfDirectionFd();
};
#endifgpio.cc
```
#include "../src/gpio.h"
#define GPIO_PATH_EXPORT "/sys/class/gpio/export"
#define GPIO_PATH_UNEXPORT "/sys/class/gpio/unexport"
#define GPIO_PATH_DIRECTORY "/sys/class/gpio/gpio%d"
#define GPIO_PATH_DIRECTION "/sys/class/gpio/gpio%d/direction"
#define GPIO_PATH_VALUE "/sys/class/gpio/gpio%d/value"
GPIO::GPIO(int id) {
Export();
OpenValueFd();
OpenDirectionFd();
}
GPIO::~GPIO() {
CloseValueFd();
CloseDirectionFd();
Unexport();
}
bool
GPIO::Exists() {
char * path;
if (asprintf(&path, GPIO_PATH_DIRECTORY, id) result++;
}
void
GPIO::Export() {
char * id;
if (Exists()) return;
if (asprintf(&id, "%d", _id) < 0)
throw "Error converting id to char.";
int fd = open(GPIO_PATH_EXPORT, O_WRONLY);
if (fd < 0)
throw "Error opening GPIO export.";
if (write(fd, id, strlen(id)) < 0)
Solution
Prefer the C++ way
As you seem to expect, one of the key issues with your code is that it's not very C++y.
with the
The C++ library for IO is typesafe, extensible, object-oriented and should be preferred. Use an
By having an exception hierarchy, you can have fine grained control over which errors to catch where. Your code will also play nice with others, who might expect your exceptions to behave like the standard ones. Want to catch everything? Catch
Remember that exceptions should always be thrown by value, and
Instead of
write
For your path name
You get type safety, easier debugging, normal scope rules; the works.
The following identifiers are reserved:
While your
Other things
These are mostly nitpicking, but you asked for a "very critical review" :-)
I'm guessing you already are, and that maybe they were removed when posting the code for review. I'm mentioning it just in case. It's nice to include
Instead of:
Instead of
write
(And use parenthesis on your casts.)
The C library function is called
Your public interface functions have names that imply that they don't change the state of the class. If they don't, make the functions
Rename functions that do change state to reflect that.
Finally,
As you seem to expect, one of the key issues with your code is that it's not very C++y.
- Prefer
std::stringover C-style strings.
std::string is better in almost every way. It takes care of its own memory and grows when needed. The speed is usually comparable to C-style strings (sometimes faster, sometimes slower). The code written using string is often more readable. For example, compare your code:if (strncmp(data, "in", 2) == 0)with the
std::string variant:if (data == "in")- Prefer
iostreamtoFILE*.
The C++ library for IO is typesafe, extensible, object-oriented and should be preferred. Use an
ifstream to read from a file and ofstream to write, or fstream for both.- Consider throwing exceptions derived from
std::runtime_errorandstd::logic_error
By having an exception hierarchy, you can have fine grained control over which errors to catch where. Your code will also play nice with others, who might expect your exceptions to behave like the standard ones. Want to catch everything? Catch
std::exception. Want to catch only your own? Do so. Want to get the error message? Call .what().Remember that exceptions should always be thrown by value, and
catched (caught) by reference(-to-const).- Prefer
enumorconsts to#defined constants.
Instead of
#define GPIO_IN 0
#define GPIO_OUT 1write
enum IODirection { GPIO_IN = 0, GPIO_OUT = 1 };For your path name
#defines, use const std::string instead. (And consider moving them into class scope as static, or into a namespace.)You get type safety, easier debugging, normal scope rules; the works.
- Avoid leading underscores in identifiers.
The following identifiers are reserved:
- Any name that begins with an underscore and is in the global namespace.
- Any name that begins with an underscore followed by a capital letter.
- Any name that contains two consecutive underscores.
While your
_id and similar variables are technically correct, don't rely on yourself or others memorizing these rules. Play it safe and move the underscore to the other side: id_, or drop it altogether.Other things
These are mostly nitpicking, but you asked for a "very critical review" :-)
#includethe proper headers.
I'm guessing you already are, and that maybe they were removed when posting the code for review. I'm mentioning it just in case. It's nice to include
#includes in the review code, too.- Let the build system take care of include paths.
Instead of:
#include "../src/gpio.h", prefer to just use #include "gpio.h" and let the preprocessor and build system take care of finding the right header for you. This makes it a lot easier to move files at a later point.- Don't cast unless you have to.
Instead of
return static_cast result++;write
return result++ != 0;(And use parenthesis on your casts.)
- Use
fclose()instead ofclose().
The C library function is called
fclose(). (This is a moot point, however, since you should prefer C++ IO anyway.)- Use descriptive names and a clear interface
Your public interface functions have names that imply that they don't change the state of the class. If they don't, make the functions
const. (Your Exists() function should at least be const. The same may apply to functions that are only reading.) Consider separating the state changing functionality from the non-state-changing functionality, and putting the latter into const functions.Rename functions that do change state to reflect that.
Finally,
data is a classic, horrible variable name. I'm sure you can do better!Code Snippets
if (strncmp(data, "in", 2) == 0)if (data == "in")#define GPIO_IN 0
#define GPIO_OUT 1enum IODirection { GPIO_IN = 0, GPIO_OUT = 1 };return static_cast<bool> result++;Context
StackExchange Code Review Q#28455, answer score: 14
Revisions (0)
No revisions yet.