0

This specific case is for Arduino, but the question applies in general. The SDK provides a Client class, from which other classes (WiFiClient, EthernetClient, and others) derive.

In my own class I have a field of type Client* which can hold any of those derived classes. I'd like to be able to delete clientfield in my class (specifically in its destructor), but because Client does not provide a virtual destructor, that would cause undefined behaviour.

Any suggestions for ways to work around this?

I could Modify Client to add the destructor, but that's not ideal since anyone using my code would have to make that same change on their system.

Jochen
  • 23
  • 4
  • Where and how is `clientfield` declared and initialized? – Ripi2 Dec 27 '20 at 00:01
  • 2
    Do you know the concrete class at the time `clientfield` is originally assigned? You could do what `std::shared_ptr` does, and capture a deleter at that time. In fact, I guess you could just use `std::shared_ptr` for `clientfield`, and have it do its magic. – Igor Tandetnik Dec 27 '20 at 00:02
  • *Any suggestions for ways to work around this?* -- Use [std::shared_ptr](https://stackoverflow.com/questions/49228156/why-does-unique-ptr-only-call-the-base-destructor-when-shared-ptr-calls-the-der) – PaulMcKenzie Dec 27 '20 at 00:06
  • 2
    Are there any virtual member functions in `Client`? If there aren't, then what good does a `Client*` pointer do you? If there are, then it's kind of odd for the library authors not to give it a virtual destructor. – Igor Tandetnik Dec 27 '20 at 00:06
  • @OP Maybe `Client` is not meant to be derived from? – PaulMcKenzie Dec 27 '20 at 00:07
  • 1
    @PaulMcKenzie as stated in the question, the SDK itself already derives WiFiClient and EthernetClient from it, so I don't believe that's the case. – Jochen Dec 27 '20 at 03:38
  • @IgorTandetnik yes there are a number of virtual member functions in ```Client```. – Jochen Dec 27 '20 at 03:39
  • @IgorTandetnik @PaulMcKenzie I was under the impression ```std::shared_ptr``` should be avoided on Arduino. A quick test shows it might actually work though, so I'll convert everything to that and see if it causes any issues. – Jochen Dec 27 '20 at 04:15
  • @Jochen If the virtual destructor is missing, and there are virtual functions in the base class, go to the authors of the library and submit a bug report. – PaulMcKenzie Dec 27 '20 at 06:29

3 Answers3

3

All you need to do is cast the pointer to its actual derived type before deleting it, for example:

if (isWifiClient)
    delete static_cast<WifiClient*>(clientfield);
else if (isEthernetClient)
    delete static_cast<EthernetClient*>(clientfield);
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • You can use `dynamic_cast` for the `is...` checks, eg: `if (WifiClient *wifi = dynamic_cast(clientfield)) delete wifi; else if (EthernetClient *eth = dynamic_cast(clientfield)) delete eth;` – Remy Lebeau Dec 27 '20 at 02:23
  • 1
    Unfortunately dynamic_cast cannot be used on Arduino, since rtti is disabled on the compiler. – Jochen Dec 27 '20 at 03:37
0

If base class doesn't have virtual destructor, you have to delete it from pointer of it derived type.

Casting is one option:

// No need of `if` as deleting null pointers is noop.
delete dynamic_cast<WifiClient*>(client);
delete dynamic_cast<EthernetClient*>(client);

Better alternative is to use std::shared_ptr which handles the (type-erased) destructor.

std::shared_ptr<Client> client = std::make_shared<WifiClient>(/*..*/);

Note, "refactoring" to std::unique_ptr<Client> (as ownership is not shared for example) would lead to your original issue.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
0

Can you use shared_ptr? It implements the dynamic dispatch in its deleter. It is important, though, that you allocate the correct shared_ptr type:

shared_ptr<Client> clientfield = make_shared<WifiClient>();

or

shared_ptr<Client> clientfield = shared_ptr<WifiClient>(new <WifiClient>());

This would be wrong:

shared_ptr<Client> clientfield(new <WifiClient>());

To make it correct again, you can pass a suitable deleter:

shared_ptr<Client> clientfield(new <WifiClient>(), default_delete<WifiClient>());
j6t
  • 9,150
  • 1
  • 15
  • 35