0

I'm writing a new serial library (eRCaGuy_Peer2Peer) to allow any 2 pins on an Arduino to be used for peer-to-peer type communication between Arduinos, withOUT interrupts or timers/counters.

Currently SPI, Serial, and I2C are pin-specific, and SoftwareSerial requires pin change interrupts. My implementation will solve these problems.

However, I first am trying to understand some things. As I study SoftwareSerial, I see it inherits from the Stream class, which inherits from the Print class.

I don't understand how Print's write is implemented. This is from Print.h:

virtual size_t write(uint8_t) = 0;
size_t write(const char *str) {
  if (str == NULL) return 0;
  return write((const uint8_t *)str, strlen(str));
}
virtual size_t write(const uint8_t *buffer, size_t size);
size_t write(const char *buffer, size_t size) {
  return write((const uint8_t *)buffer, size);
}

This is from Print.cpp:

/* default implementation: may be overridden */
size_t Print::write(const uint8_t *buffer, size_t size)
{
  size_t n = 0;
  while (size--) {
    if (write(*buffer++)) n++; //<--where is this write defined?
    else break;
  }
  return n;
}

My Questions:

  1. What does this mean? I've never seen anything like this before: virtual size_t write(uint8_t) = 0; Why the = 0?
  2. What does it mean/how does it work defining functions in .h files? I've never done this before: ex:
    size_t write(const char *str) { if (str == NULL) return 0; return write((const uint8_t *)str, strlen(str)); }

  3. Where is the write command I've marked above defined? Is Print::write() calling itself recursively?

  4. Also, why does SoftwareSerial.h call using Print::write; (shown below) when it already re-implements the virtual write function just above that?

virtual size_t write(uint8_t byte); virtual int read(); virtual int available(); virtual void flush(); operator bool() { return true; }

using Print::write;
Gabriel Staples
  • 1,385
  • 11
  • 27

2 Answers2

2

write is implemented in the class that inherits the Print class - such as HardwareSerial.

There are a number of write functions, and they are overloaded, which means that they are all distinct functions identified using their parameters as well as their name (write(uint8_t) and write(uint8_t *, size_t) are different functions). The child class overrides the write(uint8_t) function that is pure virtual1 in the main Print class, but the other write functions with other parameter lists aren't overridden and are just "used".

The using Print::write tells it "the write functions to which I am referring are from the Print class."

Using-declaration introduces a member of a base class into the derived class definition, such as to expose a protected member of base as public member of derived. In this case, nested-name-specifier must name a base class of the one being defined. If the name is the name of an overloaded member function of the base class, all base class member functions with that name are introduced. If the derived class already has a member with the same name, parameter list, and qualifications, the derived class member hides or overrides (doesn't conflict with) the member that is introduced from the base class.

I'm not entirely sure why they feel they need to use it, since the write functon(s) in Print are public and thus exposed already.

What does it mean/how does it work defining functions in .h files? I've never done this before

Exactly the same as if you had defined the function in a .cpp file, but with a few minor differences, the most important one of which is that the compiler finds it easier to inline the function (or if you're really lucky eradicate the function altogether). It's great for simple functions such as setters and getters that just set or return the value of a variable. In such cases inlining is desirable, and the compiler may even be able to replace the entire function with just direct access to the variable (if it's clever enough).

Another important thing to note with functions in .h files is that you can only really do it with class member functions or static functions. Otherwise you end up with compilation errors complaining about multiple definitions if you use the header file in more than one place.


1: A Pure Virtual function (such as virtual size_t write(uint8_t) = 0;) is one which has to be implemented in the child class. Failure to implement it results in a compilation error. The pure virtual function defines part of the interface but doesn't implement the software for it - that is down to the child class to implement.

Majenko
  • 105,851
  • 5
  • 82
  • 139
2

In the Print class the virtual size_t write (uint8_t)=0 DEFINES and names a function that accepts a byte and returns a size. ANY "virtual" function in a base class CAN be over-ridden in a derived class; however, the =0 declares that there is NO actual function in the base class -- the compiler sticks a null function pointer there. This is why pure virtual functions MUST be over-ridden in the derived (child) class.

The using Print::write; in the .h header file of your derived class tells the compiler that YOUR size_t DerCl::write (uint8_t val) {} should fill in that null pointer in the base class...this is why your write function must be declared exactly like the one in the base class. If it were not, then when the base class calls it, it would push too many values onto the stack, it would pop too many values off the stack, or it would mix up the parameters sent to the function, etc.

This "polymorphism" allows the base class (Print) to define an interface (for printing in this case) and simultaneously allows the derived class to determine where the resulting byte stream will be sent. Serial sends it to the USART's serial port. SoftSerial sends it to any two port pins (one Tx and one Rx). But you also can create classes to use SPI, TWI(i.e. CAN), a HDMC file, an LCD display, etc. And you can leverage the uniformity and the functionality of all of the Write(), print(), and println() functions that you're already familiar with by writing a single, simple size_t write(uint8_t) function for your derived class.

Gabriel Staples
  • 1,385
  • 11
  • 27
Byron
  • 21
  • 1