Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Good or bad: Calling destructor in constructor [closed]


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.

like image 267
Codebeat Avatar asked Jun 30 '18 21:06

Codebeat


People also ask

Can I call destructor in constructor?

In general, special member functions shouldn't be called explicitly. Constructor and destructor can also be called from the member function of the class. Example: CPP.

Does destructor get called if constructor fails?

Constructor Failures by Herb Sutter Incidentally, this is why a destructor will never be called if the constructor didn't succeed -- there's nothing to destroy. "It cannot die, for it never lived." Note that this makes the phrase "an object whose constructor threw an exception" really an oxymoron.

Does calling delete call the destructor?

The answer is yes. Destructor for each object is called. On a related note, you should try to avoid using delete whenever possible.

What happens if destructor is not called?

A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() . If you do not define a destructor, the compiler will provide a default one; for many classes this is sufficient.

Can you call a virtual function from a destructor?

Calling virtual functions from a constructor or destructor is considered dangerous most of the times and must be avoided whenever possible. All the C++ implementations need to call the version of the function defined at the level of the hierarchy in the current constructor and not further. You can call a virtual function in a constructor.

What happens when you explicitly call the destructor?

When you explicitly call the destructor, only the destruction is done, but not the deallocation. A legitimate use of explicitly calling destructor, therefore, could be, "I only want to destruct the object, but I don't (or can't) release the memory allocation (yet)."

What is the difference between constructor and destructor in C++?

In object-oriented programming, we use constructors to create objects, while we use destructors to destroy the objects. Classes are complex data structures that have data and functions together as a single unit. A constructor is a special member function that we use to initialize the objects.

Is it bad to call methods in a constructor?

It isn't really about calling methods, but about constructors that do too much. IMHO, calling methods in a constructor is a smell that might indicate that a constructor is too heavy. This is related to how easy it is to test your code. Reasons include: Unit testing involves lots of creation and destruction - therefore construction should be fast.


2 Answers

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++.

like image 160
Eugene Avatar answered Oct 04 '22 14:10

Eugene


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.

like image 26
Paul Sanders Avatar answered Oct 04 '22 15:10

Paul Sanders