Where does Print.cpp's write get implemented?

The Print.h class declaration seems to assign a variable to write_error but Print() isn't actually defined here:

class Print { private: int write_error; ... Print() : write_error(0) {}

However it's there in Print.cpp and calls 'write'

size_t Print::write(const uint8_t *buffer, size_t size) { size_t n = 0; while (size--) { n += write(*buffer++); } return n; }

What I'm confused about is that according to the documentation 'write' seems to be a method of 'Serial', yet the print class doesn't look like it's inherited from it. If 'write' is a standard function available to all classes, then where is it actually implemented?

Also can anyone explain what this line does: Print() : write_error(0) {}

It seems strange, but does it zero out the write_error variable whenever the Print function is called? What does the empty {} declaration do after it?

HardwareSerial is a derived class from the Stream class, which is a derived class from the Print class. That explains why write() is available to the HardwareSerial class ;)

write() is a virtual function. The basic print class doesn't know how to write to serial, I2C or whatever LCD screen you just plugged in. So long as one of the classes in the inheritance chain implements it, write will do what it's been implemented to do.

   Print() : write_error(0) {}

This is a member initialization which sets write_error to zero when the Print constructor is first run. It's a way of initializing member variables which may be classes themselves. In this case it's identical to setting the value the normal way, inside the body of the constructor {}

I've never seen write_error used anywhere. It may have been intended for some purpose that no longer exists.

Print::write() is declared in Print.h:

class Print {
    virtual size_t write(uint8_t) = 0;

That "= 0" makes it a "pure virtual" method. That means any class derived from Print (like Serial and LCD via Stream) has to provide a definition of write(uint8_t).

For Serial you will find it in HardwareSerial.cpp:

size_t HardwareSerial::write(uint8_t c)
  // If the buffer and the data register is empty, just write the byte
  // to the data register and be done. This shortcut helps
  // significantly improve the effective datarate at high (>
  // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown.
  if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) {
    *_udr = c;
    sbi(*_ucsra, TXC0);
    return 1;
  tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE;