Search code examples
c++classoopcontainersmove

Inserting a class in std::map using only move constructor


I have a class that doesn't implement neither a default constructor, nor a copy constructor, just a move constructor, and inside a function I want to insert a new instance of this class into a std::map, but when I use std::move for that, my instance still gets destroyed at the end of the function scope, isn't it supposed to move the ownership of the instance to the container ?


class Server {
  public:

    friend class Client;
    class Client {
      public:
        Client() = delete;
        Client(const Client &) = delete;
        Client(Client &&) = default;
        ~Client() {
          ::close(m_fd);
        }
        Client & operator =(const Client &) = delete;
        Client & operator =(Client &&) = default;
    
      private:
        Client(int fd, const in_addr & addr) : m_fd(fd), m_addr(addr) {}
      
        int m_fd;
        in_addr addr;
    };

  private:
    void m_acceptClient() {
      sockaddr_in addr;
      socklen_t addrLen = sizeof(addr);
      int fd = accept4(
          m_fd, reinterpret_cast<sockaddr *>(&addr), &addrLen, SOCK_NONBLOCK
      );

      if (fd == -1) {
        if (errno == EAGAIN)
          return;
        m_throwError(AcceptException());
        return;
      }

      auto client = Client(fd, addr.sin_addr);
      m_clients.emplace(fd, std::move(client));

      // client gets destroyed here
      //
      // I also tried to do
      // m_clients.emplace(fd, Client(fd, addr.sin_addr))
      // but the client also gets destroyed immediately
      // what I want to do is being able to insert a new Client
      // into the map without having to implement copy semantics for it
      // because it doesn't make sense to have them in this case
    }
    int m_fd;

Solution

  • client is getting moved, its just that all objects are destroyed at the end of the function and client was declared in the function. Your move operations need to set the instance they move from into a state where it being destroyed does not cause an issue. The contents of client though were moved into the map.

    If you don't want that to happen what you can do is directly construct the object in the map using the std::piecewise_construct_t tagged overload of emplace instead like

    void m_acceptClient() {
      sockaddr_in addr;
      socklen_t addrLen = sizeof(addr);
      int fd = accept4(
          m_fd, reinterpret_cast<sockaddr *>(&addr), &addrLen, SOCK_NONBLOCK
      );
    
      if (fd == -1) {
        if (errno == EAGAIN)
          return;
        m_throwError(AcceptException());
        return;
      }
    
      m_clients.emplace(std::piecewise_construct, 
                        std::forward_as_tuple(fd), 
                        std::forward_as_tuple(fd, addr.sin_addr));
    }