6

Break: I don't think it is the same question actually, the other question is a general question about calling destructors manually. This is at the creating process, inside the class itself. Still want to know what happen when you do this, like stated in the question below.


At first, I think it is bad, real bad. Just analysing this piece of code of a constructor (see below), made by two guys and need it to translate it to Delphi object Pascal. It must behave the same like the C-version. I don't like the style, very ugly but never mind.

Another thing, at two stages in the code it calls the destructor when fail (I suppose to close the connection however the destructor is automaticly called when deleted, why want you do this anyway?). I think that is not the way to do it or do miss something inhere?

Also, after calling the destructor, they want to throw an exception (huh?) however I think this will never be executed and cause another exeption when you manually want to access it or want to delete it.


Serial::Serial(
  std::string &commPortName,
  int bitRate,
  bool testOnStartup,
  bool cycleDtrOnStartup
) {
  std::wstring com_name_ws = s2ws(commPortName);

  commHandle =
    CreateFileW(
      com_name_ws.c_str(),
      GENERIC_READ | GENERIC_WRITE,
      0,
      NULL,
      OPEN_EXISTING,
      0,
      NULL
    );

  if(commHandle == INVALID_HANDLE_VALUE)
    throw("ERROR: Could not open com port");
  else {
    // set timeouts
    COMMTIMEOUTS timeouts;

    /* Blocking:
        timeouts.ReadIntervalTimeout = MAXDWORD;
        timeouts.ReadTotalTimeoutConstant = 0;
        timeouts.ReadTotalTimeoutMultiplier = 0;
       Non-blocking:
        timeouts = { MAXDWORD, 0, 0, 0, 0}; */

    // Non-blocking with short timeouts
    timeouts.ReadIntervalTimeout = 1;
    timeouts.ReadTotalTimeoutMultiplier = 1;
    timeouts.ReadTotalTimeoutConstant = 1;
    timeouts.WriteTotalTimeoutMultiplier = 1;
    timeouts.WriteTotalTimeoutConstant = 1;

    DCB dcb;
    if(!SetCommTimeouts(commHandle, &timeouts)) {
      Serial::~Serial();                                      <- Calls destructor!
      throw("ERROR: Could not set com port time-outs");
    }

    // set DCB; disabling harware flow control; setting 1N8 mode
    memset(&dcb, 0, sizeof(dcb));
    dcb.DCBlength = sizeof(dcb);
    dcb.BaudRate = bitRate;
    dcb.fBinary = 1;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;
    dcb.Parity = NOPARITY;
    dcb.StopBits = ONESTOPBIT;
    dcb.ByteSize = 8;

    if(!SetCommState(commHandle, &dcb)) {
      Serial::~Serial();                                    <- Calls destructor!
      throw("ERROR: Could not set com port parameters");
    }
  }

  if(cycleDtrOnStartup) {
    if(!EscapeCommFunction(commHandle, CLRDTR))
      throw("ERROR: clearing DTR");
    Sleep(200);
    if(!EscapeCommFunction(commHandle, SETDTR))
      throw("ERROR: setting DTR");
  }

  if(testOnStartup) {
    DWORD numWritten;
    char init[] = "PJON-python init";
    if(!WriteFile(commHandle, init, sizeof(init), &numWritten, NULL))
      throw("writing initial data to port failed");
    if(numWritten != sizeof(init))
      throw("ERROR: not all test data written to port");
  }
};

Serial::~Serial() {
  CloseHandle(commHandle);
};

// and there is more etc .......
// .............

Next question, what will actually happen in memory when executing this code and it calls the destructor? I am not able to execute it and debug it.

Codebeat
  • 6,501
  • 6
  • 57
  • 99
  • 8
    IMO, none of that code should be in the constructor. Constructors should be simple member initialization. Anything that requires error handling is better put into a separate method. – user3386109 Jun 30 '18 at 21:38
  • 1
    Possible duplicate of [Is calling destructor manually always a sign of bad design?](https://stackoverflow.com/questions/14187006/is-calling-destructor-manually-always-a-sign-of-bad-design) – Jean-François Fabre Jun 30 '18 at 21:38
  • @user3386109: I totally agree and that's why I call the code ugly, I will never do it like that. – Codebeat Jun 30 '18 at 21:40
  • @Jean-FrançoisFabre : It is not the same question actually, it is a general question about calling destructors manually. Still want to know what happen when you do this, like stated in the question. – Codebeat Jun 30 '18 at 21:45
  • Could you modularize this some more? If you had related classes that encapsulated some of the more ugly code in this constructor you could vastly reduce how much code is in this particular constructor. – tadman Jun 30 '18 at 21:53
  • 1
    @tadman Yeah, it is really awful. I made already an open() function with all this stuff in it and create some unstress constructors so programmer can decide which constructor to use and how to use the object. It is still 'compatible' with the C++ version. – Codebeat Jun 30 '18 at 22:00
  • @tadman You are right however the compiled version does the trick pretty well so I will see what this pascal version is capable of when all is converted properly. – Codebeat Jun 30 '18 at 22:05
  • You could abstract out some of the clutter in here and make this a lot more [RIAA](https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization) in form. Right now you're throwing bare strings, which is a big mess, so if there's anything to fix it's that. Hope you can get a handle on that. Seems like there's a few distinct resources here you're trying to encapsulate anyway. – tadman Jun 30 '18 at 22:09
  • voted to reopen; the behaviour of the code in the question is not "primariliy opinion based"; the preamble could be better worded – M.M Jul 01 '18 at 09:24
  • Whether or not `Serial::~Serial();` causes undefined behaviour depends on what the non-static data members and base classes of `Serial` are, I think – M.M Jul 01 '18 at 09:39
  • What the programmers did was rather than doing a `close` on the open handle, they instead are calling the destructor. The problem is that an object is not properly constructed until the constructor completes and this is clearly not the case. The places where the destructor is being called needs to be changed to do what actually needs to be done, closing the handle to the COM port. A destructor is not called when the constructor throws an exception. https://herbsutter.com/2008/07/25/constructor-exceptions-in-c-c-and-java/ – Richard Chambers Jul 01 '18 at 10:50
  • "on hold", ridiculous. – Codebeat Jul 01 '18 at 15:26

3 Answers3

6

This code is ugly but legal. When an exception is thrown from constructor, the corresponding destructor is never called. So calling it manually before throwing is needed to prevent the resource leak. The real bug here is not calling destructor manually in other cases before throwing exception.

Of course, a better way of doing this is having a separate RAII object that encapsulates commHandle. A unique_ptr with custom deleter can serve this role.

Any destructor beyond low-level libraries is a code smell in modern C++.

Eugene
  • 6,194
  • 1
  • 20
  • 31
4

Well, let's start by saying the obvious: don't write code this way. I can see why they did it - calling the destructor manually was a convenient way to clean up before throwing that exception, but why is it such a bad idea?

Well, the destructor is normally only called if the constructor runs to completion (so it won't be run, in the normal way of things, if the constructor throws) and this is deliberate as it allows the destructor to assume that the object has been fully initialised. A destructor of any complexity that tries to tear down an object which is not fully initialised is likely to run into trouble.

Now none of this matters in the code as written, because all we have here is a tinpot destructor that just closes the handle so, here, the code does correctly clean up before throwing (sometimes, thank you Eugene) and we can all sit down and relax. But as a programming pattern it stinks, and, now that you know what it actually does you should tidy it up when you move it to Delphi.

So, lecture over, a few specifics (in no particular order):

  • When you call a destructor manually, it's just like calling any other function - it is executed and it returns and life goes on. Specifically, the object itself is not deallocated. Doing this has value when using placement new.
  • It follows from the above that that call to throw will be executed after the destructor returns (it would be anyway, regardless).
  • Just to repeat, when a constructor throws, the destructor is not called. The object will be deallocated subsequently however, before the exception is caught (if it ever is), I believe.

If the rest of the code you have to convert is written in such a slapdash manner, I don't envy you. Constructors shouldn't fail anyway, in the general run of things, just open the port in a separate method once the object is up and running.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • Hi, thanks for the nice answer. Yeah, it is awful. I made already an open() function with all this stuff in it and create some unstress constructors so programmer can decide which constructor to use and how to use the object. It is still 'compatible' with the C++ version tho however with a big warning as comment. – Codebeat Jun 30 '18 at 22:29
  • Normally when you call the destructor inside the constructor in pascal the code become unstable because the memory is freed and the memory location invalid. It is like accessing a null pointer. So in pascal this is never possible, you will notice directly an exeption, a smack in the face ;-) – Codebeat Jun 30 '18 at 22:34
  • 3
    I do not agree with `Constructors shouldn't fail an[y]way`. Failing constructors are ok in any source that uses exceptions. – Eugene Jun 30 '18 at 22:35
  • Right. And yes, I saw your proposed `open ()` method, couldn't agree more. I'd just make sure you fully understand what that class as a whole does (including the bugs!) and then make a proper Delphi class with a proper API. I'm sure everyone involved (including your future self) will thank you for it. – Paul Sanders Jun 30 '18 at 22:37
  • @Eugene : You are right however do the maximum to avoid so that failing has something to do with less memory instead of a functionality. Throwing an exeption because a functionality failed is in some ways a little stupid, you can easily avoid this, the class, in thiscase is designed as quicky or something. – Codebeat Jun 30 '18 at 22:42
  • @Eugene OK, well, let's just say that failing a constructor should be exceptional (out of memory, typically). That's clearly not the case here, as Codebeat says. That code is just horrible. – Paul Sanders Jun 30 '18 at 22:46
  • 1
    @Codebeat (and Paul Sanders) Yes, this code is horrible, but for different reasons. Throwing exception to report errors in general, and errors occuring in constructor in particular, is good and normal - unless you can't use exceptions at all for some reason (e.g. you are in real time environment where time to handle exceptions is not acceptable). – Eugene Jun 30 '18 at 23:23
  • @Eugene Throwing exceptions for anything is not a good way to program things, that's what I agree. In fact you will break the execution point and can left other things uninitialized (which can result in other exceptions and make it very unstable). In this case it is just a functionality of the class, not an important thing of the class itself that it makes unable to function as object. The class is okay so these external connection exceptions must never be a part of the creation process. .... (see below)..... – Codebeat Jun 30 '18 at 23:55
  • .@Eugene ....(see above)..... That is using exceptions the wrong way because it doesn't say anything about the class itself. Using exceptions in other functions of the class is plausible. However, it doesn't have to be an exception anyway, it is required to function however not too critical to introduce instability what an exception can cause. True or false or errorcode will be OK, no need to raise an exception. So please don't use exceptions for anything. – Codebeat Jun 30 '18 at 23:55
  • @Eugene You removed your comment. Why? – Codebeat Jul 01 '18 at 00:07
  • @Codebeat I did not remove any comments. – Eugene Jul 01 '18 at 01:07
  • @Eugene Yes you did because and your previous latest comment edit is also gone. – Codebeat Jul 01 '18 at 02:25
  • @Eugene / Codebeat Sometimes SO removes comments that it deems superfluous - it's policy, see: https://meta.stackexchange.com/questions/19756/how-do-comments-work. WRT using exceptions for error handling, I think that largely comes down to personal preference. The problem with failing a constructor is that it _forces_ you to throw an exception, and as doing that is not my personal preference for handling errors in general I'm not too keen on it. – Paul Sanders Jul 01 '18 at 05:04
2

When you throw from the constructor, it will call the destructor of any object constructed so far: the member variables and the inherited classes (section 15.2/2).

  1. An object of any storage duration whose initialization or destruction is terminated by an exception will have destructors executed for all of its fully constructed subobjects

If you call the destructor manually, their destructor will be also called (section 12.4/8).

  1. After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes ...

Therefore the destructor of member variables will be called twice. Formally, calling twice a destructor is undefined behavior. (You may get away with it if they all had empty destructor.)

If you really need a clean solution, wrap the parts that need to be cleaned into a class and make it a member variable. Call the initialisation from it and if you throw, you are guaranteed that it will be cleaned. You even get kudo points for applying RAII.

Michael Doubez
  • 5,937
  • 25
  • 39
  • 1
    Right, although none of that appears to apply here. There's a good answer about that here: https://stackoverflow.com/questions/32323406/what-happens-if-a-constructor-throws-an-exception/32323458. Anyway, the code is horrible, trash it. – Paul Sanders Jun 30 '18 at 22:52
  • Hi, thanks for the answer. I doubt it will called the destructor when raising an exception at the constructor, more like at @Eugene answer, introducing a memory leak because they don't use the evil part consequently. – Codebeat Jul 01 '18 at 00:22
  • @PaulSanders : Sadly I can't trash it, need to solve it. ;-) However understand your feelings. – Codebeat Jul 01 '18 at 00:24
  • @Codebeat That's right, the destructor won't be called when the constructor throws an exception here. And by 'trash the code', I meant start over and write it properly, certainly that bit, as I now know you plan to do. – Paul Sanders Jul 01 '18 at 05:06
  • 1
    The destructor function won't be called in the case of exception in the constructor, but the destructor of members will. Now I see that there is much focus on the quality of the code, then rewrite your question. – Michael Doubez Jul 01 '18 at 06:07