3

I usually don't call destructors explicitly. But I'm designing TCP server class, which looks something like this:

class Server {
public:
    Server() {
        try {
            WSADATA wsaData;
            if (WSAStartup(MAKEWORD(2, 2), &wsaData))
                throw std::runtime_error("WSAStartup function failed.");
            ...

            if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
                throw std::runtime_error("'socket' function failed.");
            ...
        }
        catch (std::exception& ex) {
            this->~Server();
            throw;
        }
    }

    ~Server() {
        if (m_scListener != INVALID_SOCKET) {
            closesocket(m_scListener);
            m_scListener = INVALID_SOCKET;
        }
        WSACleanup();
    }
private:
    SOCKET m_scListener = INVALID_SOCKET;
}

Is the code above considered as bad practice or design? What's recommended way of designing this? I wrote this way, because constructors can't return NULL. Should I make constructor private, and write static method that creates an instance of the Server class?

===== U P D A T E =====

OK, summarizing the answers, I came to this conclusion:

  • Explicitly calling a destructor is generally a bad idea, even if it works as intended, this is something unusual, and other C++ programmers who will be dealing with your code may get confused with this approach. So it's best to avoid explicitly calling destructors.

  • Breaking my original RAII class into micro RAII classes looks like a good solution. But I'm afraid that there are too many API calls in my real code that need for clean-ups (closesocket, CloseHandle, DeleteCriticalSection, etc...). Some of them are called only once and are never reused, and moving all of them into separate RAII classes seems too fanatical to me. This will also increase my code.

  • In my opinion the most helpful answer is from M.M:

A better solution would be to keep the initialization code in the constructor, and call the cleanup function before throwing out.

Following the M.M's advice I rewrote my code this way:

class Server {
public:
    Server() {
        WSADATA wsaData;
        if (WSAStartup(MAKEWORD(2, 2), &wsaData))
            ThrowError("WSAStartup function failed.", true);
        ...

        if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
            ThrowError("'socket' function failed.", true);
        ...
    }

    ~Server() { CleanUp(); }

private:
    SOCKET m_scListener = INVALID_SOCKET;

    void ThrowError(const char* error, bool cleanUp) {
        if (cleanUp)
            CleanUp();
        throw std::runtime_error(error);
    }

    void CleanUp() {
        if (m_scListener != INVALID_SOCKET) {
            closesocket(m_scListener);
            m_scListener = INVALID_SOCKET;
        }
        WSACleanup();
    }
};

I believe this design follows the RAII pattern, but only one class instead of 3-4 micro RAII classes.

Tiber Septim
  • 315
  • 3
  • 11
  • 1
    Calling the destructor has a special meaning, it ends the lifetime of the objext. When the constructor throws, the lifetime never started. You can't call a destructor from a constructor. – François Andrieux Nov 06 '20 at 04:03
  • Actually I tested this putting MessageBox inside the destructor, and it showed up when exception was thrown from the constructor. – Tiber Septim Nov 06 '20 at 04:05
  • 2
    You're playing with undefined behavior here. The insidious thing about undefined behavior is that it can look like it's working even when it isn't. – Mark Ransom Nov 06 '20 at 04:09
  • 2
    Calling destructors explictly *at all* is poor practice. In this case it is completely unnecessary. – user207421 Nov 06 '20 at 04:24
  • `pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol` -- Look at your design -- what sets these values? What if the user wants different values here? Bottom line -- your constructor is doing too much work. What I highlighted should be the values you would be setting in the constructor as individual members. Then the user of your class could call a `start()` or similar function that does the actual work of initializing WinSock with those member values. Then all of the shenanigans being played calling the destructor explicitly become moot. – PaulMcKenzie Nov 06 '20 at 05:07
  • @PaulMcKenzie these values are passed as arguments to the constructor. Code I posted here is simplified version of my real code, I just wanted to make my post more compact and removed some irrelevant stuff from code sample, and replaced some portions of code with "...". – Tiber Septim Nov 06 '20 at 05:43
  • You really shouldn't have removed that part of the code. It gives the false impression that the `Server` class constructor takes no arguments, and you're starting the Server with hardcoded or non-changeable values. – PaulMcKenzie Nov 06 '20 at 05:44
  • I removed part of my code because my question is not about configuring the Server class, my question is more about class design. – Tiber Septim Nov 06 '20 at 05:50
  • @TiberSeptim The one issue I still have with your final edit is that the class still lacks having the user optionally get manual control of starting and stopping the server. For a real life example, look at how the `ifstream / ofstream` classes are designed. Even though you can open/create a file on construction, and have the file closed automatically on destruction, there are still `open()` and `close()` member functions available to the user. – PaulMcKenzie Nov 06 '20 at 14:52
  • 2
    "Some of them are called only once and are never reused, and moving all of them into separate RAII classes seems too fanatical to me. This will also increase my code." this is completely untrue, if done properly it will make your code shorter and cleaner. – Slava Nov 06 '20 at 16:02
  • @PaulMcKenzie it lacks Start/Stop because the Server is supposed to start when my program is launched and stop when program exits. Otherwise I agree with you that WinSock initialization should be done in Start function and released in the destructor. – Tiber Septim Nov 07 '20 at 08:02

6 Answers6

5

Is explicitly calling destructors from constructors bad practice in C++?

Yes. If you call a destructor of an object that hasn't been constructed, the behaviour of the program is undefined.

Having undefined behaviour is a bad thing. It should be avoided whenever possible.


What's recommended way of designing this?

Follow the Single Responsibility Principle (SRP), and the Resource Aquisition Is Initialisation (RAII) pattern.

In particular, your Server has too many responsibilities. You should create a separate class that manages a socket. Within the constructor of that class, call scoket and within the destructor, call of that class, call closesocket. Maintain the class invariant that the contained socket always valid (closable) or INVALID_SOCKET and always unique if valid and is never leaked (i.e. the value is never overwritten without closing first). This is the RAII pattern.

Create a similar wrapper for wsa data.

Within Server, store members of these wrapper types. Server won't then need a custom destructor or other special member functions since those are handled by the members which manage themselves.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • If I will follow SRP that way in Win32 API then every single API function will have it's own class. I don't think that my case violates the SRP. – Tiber Septim Nov 06 '20 at 04:32
  • @TiberSeptim `If I will follow SRP that way in Win32 API then every single API function will have it's own class.` I doubt ever single Win32 API function needs cleanup. And I doubt your program needs to call every single Win32 API call. If you call a particular create/destroy pair of functions only once, then it may be fine to use a generic RAII wrapper i.e. `std::unique_ptr` as suggested in another answer. But a reusable named class is particularly useful when the functionality is well, reused. `I don't think that my case violates the SRP.` I think it does. – eerorika Nov 06 '20 at 04:37
4

Destructors should only be called by a fully constructed object.

You can make an Init() and CleanUp() function instead of putting the setup code in the constructor. This will also make your Server object faster to construct.

class Server {
public:
    Server() = default;

    bool Init() {
      try {
            WSADATA wsaData;
            if (WSAStartup(MAKEWORD(2, 2), &wsaData))
                throw std::runtime_error("WSAStartup function failed.");
            ...

            if ((m_scListener = socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol)) == INVALID_SOCKET)
                throw std::runtime_error("'socket' function failed.");
            ...
            return true;
        }
        catch (std::exception& ex) {
            return false;
        }
    }

    void CleanUp() {
        if (m_scListener != INVALID_SOCKET) {
            closesocket(m_scListener);
            m_scListener = INVALID_SOCKET;
        }
        WSACleanup();
    }

    ~Server() {
      CleanUp();
    }

private:
    SOCKET m_scListener = INVALID_SOCKET;
};

Caller-side code:

Server server;
if (!server.init()) {
   server.CleanUp();
}
impulse
  • 206
  • 2
  • 15
  • 1
    This is what I would do - cleanup will happen even if the consumer forgets to call it manually later on. – Alex Nov 06 '20 at 04:10
  • Note that this ends up calling `WSACleanup` whether `WSAStartup` has ever been succesfully called. And it is called twice. – eerorika Nov 06 '20 at 04:39
  • 3
    this is called "two-phase initialization" and generally frowned on, because it introduces the possibility of "zombie" objects whose initialization failed. A better solution would be to keep the initialization code in the constructor, and call the cleanup function before throwing out . – M.M Nov 06 '20 at 04:45
  • I totally agree with you. I prefer to avoid this approach too. I just thought why not to call destructor explicitly, it's same as regular member function of a class but called automatically just before deallocation. I just can't fully understand why calling destructors explicitly as in my case is bad practice. As for me destructor is no different from regular member function. – Tiber Septim Nov 06 '20 at 06:01
3

What's recommended way of designing this?

I would say: even more RAII. Something like:

class WSARaii
{
public:
    WSARaii()
    {
        if (WSAStartup(MAKEWORD(2, 2), &wsaData))
            throw std::runtime_error("WSAStartup function failed.");
    }
    ~WSARaii()
    {
        WSACleanup();
    }
    WSARaii(const WSARaii&) = delete;
    WSARaii& operator =(const WSARaii&) = delete;

private:
    WSADATA wsaData;
};

class Socket
{
public:
    Socket(..) : m_scListener(socket(pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol) {
        if (m_scListener == INVALID_SOCKET)
            throw std::runtime_error("'socket' function failed.");
    }
    ~Server() {
        if (m_scListener != INVALID_SOCKET) {
            closesocket(m_scListener);
        }
    }
private:
    SOCKET m_scListener
};

And finally

class Server {
public:
    Server() : wsa(), socket(..) {}

private:
    WSARaii wsa;
    Socket socket;
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Thanks for the useful RAII example! It looks very clean, I like it. But I'm afraid I have too many APIs in my code that require wrapping them in RAII. Some functions are called only once (but requires clean-up like CloseHandle(), etc...). Does it make sense to wrap all of them in RAII? – Tiber Septim Nov 06 '20 at 09:30
  • 1
    @TiberSeptim *But I'm afraid I have too many APIs in my code that require wrapping them in RAII* -- If there is a chance that an exception is thrown between the time the "initialize" and "end" functions are called with these objects, what choice do you have, other than `try/catch` blocks all over the code? Your best choice is to identify these classes and wrap them in some sort of RAII wrapper (or use `std::unique_ptr` with custom deleter). – PaulMcKenzie Nov 06 '20 at 12:25
  • Thank you for the advice. Can you provide an example of using std::unique_ptr with custom deleter. I'm not familiar with this code pattern. – Tiber Septim Nov 06 '20 at 12:30
  • 1
    You also may implement some generic RAII class (as [Finally](https://stackoverflow.com/questions/44520797/fastest-finally-for-c/44521132#44521132)) to avoid one class by API. – Jarod42 Nov 06 '20 at 12:35
1

I don't know what would happen there on a technical level, but it doesn't look good. I would recommend not doing that. It's far easier and less error-prone IMO to initialize high level systems like networking and whatnot inside a separate Init() method in your class. That way you can safely create an instance, call its Init() method, check the result, and delete (or call a Destroy(), or both) on failure.

I would only assign default values inside the constructor and let outside code call your destructors with delete.

Alex
  • 310
  • 1
  • 6
  • Of course, Init/Destroy can solve this, but there is a risk that a consumer of the Server class may forget to call Destroy, but destructors are called automatically. That's why I decided to put this in constructor/destructor. – Tiber Septim Nov 06 '20 at 04:02
  • In that case, you could check if WSA was initialized in your destructor by some simple bInitialized boolean that was set to true, and call Destroy() to shut it down if it has. Also make Destroy() do the same check before performing its cleanup, so whether they Destroy() or not before deleting, it will always get called exactly once. – Alex Nov 06 '20 at 04:06
1

Is the code above considered as bad practice or design?

Yes calling consrtuctor or destructor explicitly is almost always wrong, except very seldom cases and this is not the one.

What's recommended way of designing this?

Recommended way is to use RAII. In this case you can use std::unique_ptr with custom deleter which calls closesocket() etc. Or you can create your own wrapper. Then you can safely throw exception and make sure that resources that initialized get cleaned properly.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • Can you provide some pseudo code as an example? I'm not very good in modern C++. Mostly program on C#, and sometimes do some low-level stuff in C/C++. Also, doesn't my code follow the RAII? Initializing the WinSocket stuff in the constructor and releasing them in the destructor? – Tiber Septim Nov 06 '20 at 08:05
  • 1
    @TiberSeptim no your code does not follow, you need an object per resource. Jarod42 already shown how to do it with RAII. Here is example how to use `std::unique_ptr` with `FILE` https://gist.github.com/meshell/876f1e01c7e72994b126 – Slava Nov 06 '20 at 15:55
1

Taking a look at your design, you have this code in the socket() call in the constructor:

pAddr->ai_family, pAddr->ai_socktype, pAddr->ai_protocol.

What if the user of the Server class wants to use different socket type, protocol, etc. before the socket() is opened? They have no recourse, as they are locked into the values you are using in pAddr (you never mentioned where you are getting these values, but they certainly are being set prior or within the Server constructor).

If you made those socket arguments individual members of the class, that opens up the class design so that it is not necessary to invoke an ill-conceived call to the destructor, since the constructor would not get involved in calling socket() or even WSAStartup.

class Server 
{
    public:
        void set_family(int family) { m_family = family; }
        //.. other setters

        void start()
        {
            WSADATA wsaData;
            if (WSAStartup(MAKEWORD(2, 2), &wsaData))
                throw std::runtime_error("WSAStartup function failed.");

            if ((m_scListener = socket(m_family, m_type, m_protocol)) == INVALID_SOCKET)
                throw std::runtime_error("'socket' function failed.");
        }

        void stop()  
        {
            if (m_scListener != INVALID_SOCKET) 
            {
                closesocket(m_scListener);
                m_scListener = INVALID_SOCKET;
            }
            WSACleanup();
        }

        ~Server() noexcept
        {
           try 
           { 
              stop();
           }
           catch(...) { } // add any additional catches above this, 
                          // but make sure no exceptions escape the destructor
       }  

    private:
        SOCKET m_scListener = INVALID_SOCKET;
        int m_family = AF_INET;
        int m_type = SOCK_STREAM;
        int m_protocol = IPPROTO_TCP;
 };

This (to me) is a cleaner and more flexible interface that does not need to have destructors called explicitly. The actual initialization of WinSock and connection to the socket is only done in the start() call.

In addition, the parameters to socket are member variables that are initialized to basic values, but can be changed with the set... functions before the call to Server::start().

The other addition is the try/catch within the destructor of Server. Note that this was done to ensure that anything that can be thrown does not escape the destructor call, else a std::terminate would be invoked.

PaulMcKenzie
  • 34,698
  • 4
  • 24
  • 45
  • these values are passed as arguments to the constructor. Code I posted here is simplified version of my real code, I just wanted to make my post more compact and removed some irrelevant stuff from code sample, and replaced some portions of code with "...". – Tiber Septim Nov 06 '20 at 05:46