-
-
Notifications
You must be signed in to change notification settings - Fork 7k
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
Virtual destructor for base class "Print" #4466
base: master
Are you sure you want to change the base?
Conversation
Abstract classes must have virtual destructors, so when you delete an object through a pointer the proper destructor can be called. Avoids two compiler warnings in some situations: - deleting object of abstract class type 'Print' which has non-virtual destructor will cause undefined behaviour [-Wdelete-non-virtual-dtor] - deleting object of polymorphic class type '...' which has non-virtual destructor might cause undefined behaviour [-Wdelete-non-virtual-dtor] More info: http://stackoverflow.com/questions/461203/when-to-use-virtual-destructors
Doesnt this add a lot overhead? |
There are lots of virtual functions across all Arduino's base libraries. For example, in the same file, the What's more, I think it is more important that these base libraries are correct, so compiling any project using them should not raise any compiler warning. What is it that the library has no overheads if its behavior is not correct? But anyway, overhead added here will be minimal. No overhead would appear if you don't use pointers. |
Please check that again. I am not sure if it will not add any overhead. From my experience it will always add an overhead, even if not used. Thats the only thing that I am worried about. And those other virtual functions in the code base are essential. But they mostly avoid it for a good reason. Not sure if there are other solutions to fix your problem. |
But compared to how many times the There's lot of information where virtual destructors should be used, and one of the causes is when on class that will be derived, with some exceptions which are not this case. References: (1) (2) and more Without virtual, when I delete a There are no other solutions to fix my code. Then, my question is: Isn't it important that the library has the correct behaviour? Thanks |
What i mean: If its just in the case of using it: why not. Good idea then. |
@NicoHood Here is a small test case: struct Base{
virtual ~Base(){ Serial.println("base dtor"); }
virtual void foo() = 0;
};
struct Derived : Base{
~Derived(){ Serial.println("derived dtor"); }
virtual void foo(){ Serial.println("derived foo"); }
}; Then there are three basic usage scenarios. 1. An object, which is how things are most commonly used:Derived d;
d.foo(); This worked fine with or without 2. Then there is dynamic memory.Derived *d = new Derived;
d->foo();
delete d; This also worked fine, with or without a 3. Lastly there is dynamic memory using a
|
You probably meant 65 or 66 bytes there? I also think this is the full snippet, not the overhead of adding the As for the overhead of adding the Can someone try a few example sketches using serial or ethernet (or some other
I'm not sure if this is true - if In general, objects like @Chris--A your last suggestion, using a protected destructor, will essentially just enforce the current behaviour of not supporting a virtual destructor / deleting things through a |
@matthijskooijman Here it is my usecase. This is an oversimplified logger class, which logs messages to some streams at the same time, of different kinds: class MultiStream {
private:
static const uint8_t MAX = 12;
Stream* streams[MAX];
uint8_t count;
public:
MultiStream() {
this->count = 0;
}
~MultiStream() {
for (uint8_t i = 0; i < this->count; i++) {
this->streams[i]->flush();
delete this->streams[i];
}
}
void add(Stream* stream) {
this->streams[this->count] = stream;
this->count++;
}
void print(String msg) {
for (uint8_t i = 0; i < this->count; i++) {
this->streams[i]->print(msg);
}
}
};
MultiStream* ms = new MultiStream();
ms->add(new File("foo.log", O_WRITE));// write to a file
ms->add(new SoftwareSerial(1, 2));// write to gprs port
ms->print(F("testing..."));
delete ms; In this case, the deletion is not closing nor the file neither the software serial. It is also not freeing the allocated memory -if any- from derived classes. In my project, where I use lots of classes and inheritance between them (and of course virtual methods and destructors), the overhead of the compiled program when adding this virtual destructor is less than 100 bytes (from 117,278 to 117,346). Maybe the compiler can share some machine code for the v-tables, so for my project there isn't so much overhead as there is for simple programs. |
Sorry, forgot about this one.
No, 656. Compiling the sketch below (Mega) is 2858 bytes with a struct Base{
virtual ~Base(){ Serial.println("base dtor"); }
virtual void foo() = 0;
};
struct Derived : Base{
~Derived(){ Serial.println("derived dtor"); }
virtual void foo(){ Serial.println("derived foo"); }
};
void setup() {
Serial.begin(9600);
Derived d;
d.foo();
}
void loop() {}
This is what I thought also, they appear to not be implemented how I'd expect. From inspection of the generated assembly, the compiler generates something for each class in the hierarchy, even if it is empty or not implemented in code. When using the print class, there may be quite a few calls: It is quite difficult to follow using an Arduino example, my ASM skills are not great, I'm a C++ man. However you can see the execution jumping all over the place before it gets to the print destructor. Even this sketch has a difference of 720 bytes when using a virtual destructor. void setup() {
Serial.begin(9600);
}
void loop() {
Serial.print("I hate traffic lights");
}
Exactly! |
I had a closer look at the difference in generated asm. It seems that when adding the virtual destructor to
Adding As you would expect from this, if you have a sketch that already references I was hoping that the 1.6.10 IDE version, with LTO enabled, would perhaps solve this issue. However, it turns out the above testing already was running with LTO enabled (my Arduino setup is a bit of a mixture between versions), so that does not seem to help. In that light, I think that adding a virtual destructor isn't something we can do, at least not until the compiler becomes smarter about this. Adding hundreds of bytes to every sketch that uses Adding a non-virtual, protected destructor still adds the global dtors overhead (26 bytes), but not the rest. However, using this provides the same result, but without any overhead:
I think that adding this would be a good way to at least document the absence of a virtual destructor. @Ivan-Perez, for your usecase, I would suggest adding a wrapper class
In your logging code, you can then use Note that I have not tested the above classes (not even compile-tested), so |
Couldn't this be optional behind an |
Hi. |
That's 25% of the RAM on an Arduino Micro. |
In that case you should probably not use C++. ;) |
Reading back, I think those 656 bytes refer to flash usage, not RAM usage. |
@matthijskooijman is there a chance to get virtual destructors into Print class? |
You're too quick for me to correct my error. It's 2% of the flash.
This is a one time overhead for making any use of virtual destructors. Making the destructor protected looks like a reasonable solution but the lack of a virtual destructor causes problems for other derived class hierarchies that just want to be |
It's not my call, but personally I would be hesitant if this adds 600bytes of flash usage to all sketches that are not yet using dynamic memory (which is how I read the earlier discussion). Maybe new compiler versions are smarter about this and manage to optimize this stuff more, that could be interesting to recheck maybe. |
Perhaps adding virtual destructors on platforms that are not short on memory? |
Abstract classes must have virtual destructors, so when you delete an object through a pointer the proper destructor can be called.
Lack of this virtual constructor gives two possible compiler warnings in some situations:
More info: http://stackoverflow.com/a/461224/1852787
Example of undefined behaviour code without the virtual destructor:
This pull request fixes both possible warnings.