-
Notifications
You must be signed in to change notification settings - Fork 34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deriving from TwoWire pointless? #28
Comments
Sorry, just noticed this has also been brought up in #27. |
I would argue that deriving from the TwoWire class in Wire.h doesn't solve the problem that it seems to be trying solved and creates some new problems. My issue with inheriting TwoWire from <Wire.h> is that beyond the inheritance issues, it creates new issues, like breaking existing code and the documentation of that existing code. So overall, IMO, it is not a good idea. It breaks existing code (like my hd44780 library) that has the ability to use the SoftwareWire library since a prior way of using the SoftwareWire library no longer compiles much less work. In terms of converting everything in the API to use virtual functions, while using virtual functions can look appealing from a development & maintenance perspective, the problem with making everything virtual methods/functions is that it will make the code bigger and slower, will use more precious RAM, and if that isn't bad enough, ALL virtual functions will be linked into the image including methods/functions that are never used. (virtual functions cannot be optimized out by the compiler/linker the same way that regular functions can) Trying to make things transparently "just work", like the Wire library is an impossible problem to solve. In terms of helping out some 3rd party libraries that depend on a Wire object, it might be able to do that in some cases; however, it breaks other usage cases that used to work. For example prior to the 1.5.1 release it was possible to use SoftwareWire and have the sketch create the Wire object using whatever pins the user wanted to use.
This was nice since it was all self contained in the sketch, allowed the user to configure any sda, an scl pins he wanted, and could provide a global Wire object for any/all other libraries that needed a Wire object assuming that other libraries used by the sketch didn't include <Wire.h> them. Overall, there is no way to fully solve the problem of sketches and libraries that want to use <Wire.h> and the Wire object, since the issues are all outside of SoftwareWire, in the Wire library and the 3rd party libraries that have been hardcoded to use the Wire library Now you could play some very ugly games and cheat by knowing how the Arduino IDE handles its libraries and include paths. SoftwareWire could provide its own Wire.h header file inside the SoftwareWire library. The IDE creates an include path for library headers based on the libraries it "sees" in the sketch and then other libraries it "sees" from those libraries. Yes it is VERY ugly, but the SoftwareWire library could take advantage of this to make things work better and no longer by tied an external TwoWire class. The real answer, is that libraries that depend on a "Wire" library API need to use templates so they can be passed the name of the "Wire" object and its class and morph accordingly. Eventually, I'll be moving that way with my hd44780 library to escape from all this Wire library craziness. |
I agree on some of your observations, but not all of them. If we really want to solve this issue once for all, we have to come up with a meaningful proposal to the Arduino team, one that balances resource usage, ease of customization and backwards compatibility. Using templates has the disadvantage that it does not enforce uniformity of interfaces i.e.: different implementations might have slightly different methods ( I am no expert as to how C++ code gets compiled, but I think I understand why the compiler cannot optimize away virtual methods even if they are not called. But then I think the cleanest solution would be to have a TwoWire class that is fully abstract, i.e. with only pure virtual methods. Then Wire would be a concrete implementation of it, and SoftwareWire would be another one. Interface uniformity would be guaranteed and including the latter would in no way link in anything from the former. I would also expect the compiler being able to do more clever optimizations if it only sees a fully abstract class and a single implementation of it (I'm not actually sure of this, as again I am no compiler guru, but in theory I don't see why it should not). What do you think? |
gcc used to have the ability to optimize out unused virtual functions in certain situations. On the technical side of things of enhancing the Wire library, it is much more than just convincing Arduino.cc to make a change (see more on that below), It requires all the 3rd party core vendors to buy into it as well. At best, even if Arduino.cc went along with it, it would likely be years, if ever, before it propagated around to all the cores and they stopped shipping their own full/self-contained version of the Wire library. And then there may also be implications for 3rd party libraries that use the Wire library. They may also have to make changes depending on the level of backward compatibility offered by a new Wire library and Wire API. Getting everyone to switch over to something new and fully depend on Arduino.cc for a new version of Wire library & Wire API is a big deal at this point. IMO, the only way that something like this might work would be come up with a new i2c library rather than try to change/update/migrate the existing Wire library. And even then, like I said, using virtual functions is nice for creating an internal layered API, but there is a cost. I know I sound pretty negative, and I could be mistaken, but I'm assuming that there will be quite a bit of resistance to adopt a new Wire library design by the 3rd party developers, particularly for low resource environments like the AVR. Especially since for the most part, it would be a lot of work to switch over, and there wouldn't be much, if any, benefit to them, but there would be some downsides. In terms of getting the Arduino team to change something, that is a very difficult thing to do. I've had a few successes doing this but it is pretty difficult, very time consuming, and most of my attempts and attempts I've seen by others have failed, including when patches/pull requests have been offered and the update was transparent and fully backward compatible with all existing code.
There are many more. |
I have decided that for my hd44780 library, I will advice my users to avoid the 1.5.1 library version and to stick with version 1.5.0 as 1.5.1 is not only larger due to it dragging in the Wire library code, even if not used, but version 1.5.1 is not compatible with the way the hd44780 library was using it due to the inclusion of the <Wire.h> header file. On a side note, IMO, from a SemVer version numbering perspective, the change to inheriting TwoWire is not a patch number release bump, it is a major version number since it has changed things in a way that is not backward compatible. i.e. it should have bumped to version 2.0.0 |
mmm |
In my opinion, I don't see the value of inheriting TwoWire, but it isn't my library. In terms of going back to 1.5.0, it looks like a few fixes were done since the switch to inheriting TwoWire, so if you do decide to revert back to pre TwoWire inheritance, those updates would need to be pulled back in. I will say if you do decide to back out 1.5.1, |
FYI, this PR (arduino/ArduinoCore-avr#396) attempted to convert about 21 Reading over the comments in this issue, I basically agree with everything @bperrybap has written. There is little advantage of inheriting from BTW, TwoWire* myWire = new SoftwareWire();
delete myWire; // calls ~TwoWire::TwoWire(), NOT ~SoftwareWire::SoftwareWire() So the virtual destructor does not help at all. But it does increase flash memory consumption by about 500-600 bytes on AVR processors, because the virtual destructor pulls in |
I have tried to use this library. It's an interface library to a MCP23017 IC and I chose it because it allows passing a reference to an arbitrary TwoWire-derived library to be used for i2c communications. Of course the idea was to use it with your SoftwareWire library, as it is the only library of its kind that derives from TwoWire, which is a great idea!
The library saves a reference to the passed object internally. This reference must of course be of type
TwoWire&
. It is then used every time i2c bus operations are to be performed.So does all of this work? Well, unfortunately it would be too good to be true :(. The problem is that a lot of the methods that you had to override (let's just consider
begin/endTransmission()
for instance) are NOT declared virtual in the base TwoWire class. This means that every time they are called through a pointer/reference to the base class (as the MCP23017 library does), the original implementations of the base class themselves get called, instead of your overridden ones.This is NOT a bug in your library, but likely an oversight (or questionable design decision) by the developers of the original TwoWire, who probably never thought that their class could be subclassed. Nevertheless, this makes the whole point of deriving your class from
TwoWire
useless. To make something useful out of it, we would need to ask the virtual qualifier to be added to all the relevant methods in the original Wire library.I think we can try to do this, what's your opinion? This would open a lot of other interesting possibilities, even for other libraries :).
The text was updated successfully, but these errors were encountered: