代码之家  ›  专栏  ›  技术社区  ›  shmoo6000

C++在映射中映射()时防止析构函数调用

  •  2
  • shmoo6000  · 技术社区  · 6 年前

    我有一个管理资源(网络套接字)的类。

    我写了一节课 ConnectionHandler 它处理从调用创建的网络套接字 accept() .

    本课程的设计考虑了RAII,当 接受() 调用时,返回的套接字被放入 连接处理程序 ,当它超出范围时,析构函数将关闭套接字。

    我也在追踪我所有的公开信息 连接处理程序 通过将它们保存在映射中(将套接字地址(IP:端口)映射到 连接处理程序 与该地址相对应)。

    我“安置”这些有问题 连接处理程序 不过,它在地图上。

    我做到了,所以 连接处理程序 不能被复制(至少我相信我是这样做的),但是打电话的时候 std::map::emplace , the 连接处理程序 调用的析构函数(可能是删除在该行某个位置创建的临时对象)并关闭套接字。

    正如您所看到的,这会产生一个问题,因为现在套接字不能在程序中进一步使用。

    有什么方法可以阻止 连接处理程序 将其放入 std::map ?

    这是 连接处理程序 : 头文件:

    class ConnectionHandler
    {
        private:
            constexpr static long BUFFER_SIZE = 1 << 12;    // 4K Buffer
    
            SocketAddress peer;             // This is kept around to be able to produce clear exception messages when something goes wrong
            SocketFileDescriptor socket;    // using SocketFileDescriptor = int;
    
        public:
            ConnectionHandler() noexcept = delete;                                      // Default Constructor
    
            explicit ConnectionHandler(SocketFileDescriptor socket, const SocketAddress& socketAddress) noexcept;   // Value Constructor
    
            ConnectionHandler (ConnectionHandler&& handler) noexcept;                   // Move Constructor
    
            ConnectionHandler (const ConnectionHandler& handler) = delete;              // Delete Copy Constructor
    
            ConnectionHandler& operator= (ConnectionHandler&& handler) noexcept;        // Move Assignment Operator
    
            ConnectionHandler& operator= (const ConnectionHandler& handler) = delete;   // Delete Copy Assignment Operator
    
            ~ConnectionHandler();                                                       // Destructor
    
            void close() noexcept;                                                      // Allow the owner to manually close the socket if necessary
    
            void set_blocking (bool blocking) const;                                    // Make the socket either blocking or non-blocking
    
            friend std::ostream& operator<< (std::ostream& stream, const ConnectionHandler& handler);   // Receive data from the socket
    
            friend std::istream& operator>> (std::istream& stream, const ConnectionHandler& handler);   // Send data to the socket
    };
    

    执行情况:

    ConnectionHandler::ConnectionHandler(SocketFileDescriptor socket, const SocketAddress& socketAddress) noexcept: peer(socketAddress), socket(socket)
    {
    }
    
    ConnectionHandler::ConnectionHandler(ConnectionHandler&& handler) noexcept: peer(std::move(handler.peer)), socket(handler.socket)
    {
    }
    
    ConnectionHandler& ConnectionHandler::operator=(ConnectionHandler&& handler) noexcept
    {
        this->peer = std::move(handler.peer);
        this->socket = handler.socket;
        return *this;
    }
    
    ConnectionHandler::~ConnectionHandler()
    {
        if (this->socket > 0)   //  Check if the socket has been closed manually
                                //  Don't bother setting the socket to -1, the object is being destroyed anyway
        {
            std::cout << "Closing socket from destructor " << this->socket << std::endl;
            ::close(this->socket);
        }
    }
    
    void ConnectionHandler::close() noexcept
    {
        std::cout << "Closing socket from close() " << this->socket << std::endl;   // Close the socket manually and indicate it is closed by setting it's value to -1
        ::close(this->socket);
        this->socket = -1;
    }
    
    [...]
    

    这就是SocketAddress类的外观(我知道,它不适用于IPv6):

    class SocketAddress
    {
        private:
            std::array<std::uint8_t, 4> ip;
            std::uint16_t port;
    
        public:
            friend void swap (SocketAddress& sa1, SocketAddress& sa2) noexcept;
    
            SocketAddress() noexcept;
    
            explicit SocketAddress(struct sockaddr_storage* sockaddrStorage);
    
            SocketAddress (const SocketAddress& address) = default;
    
            SocketAddress (SocketAddress&& address) noexcept = default;
    
            SocketAddress& operator= (SocketAddress address);
    
            friend bool operator< (const SocketAddress& lhs, const SocketAddress& rhs) noexcept;
    
            friend std::string to_string(const SocketAddress& address) noexcept;
    };
    

    最后,下面是创建ConnectionHandler并将其放置在映射中的代码:

    void Server::listenLoop()   // acceptLoop() would be a better name
    {
    
        struct sockaddr_storage remoteAddr;
    
        while(!stop)    // stop is a std::atomic<bool>
        {
            [...]   // accept() connections in a loop
    
            SocketAddress address = SocketAddress(&remoteAddr);
            this->incomingSockets.emplace(std::make_pair(address, ConnectionHandler(childFileDesc, address)));
        }
    
        [...]
    }
    

    此函数运行在与主线程分离的线程上,该线程保留在服务器对象中,并加入服务器对象的析构函数。

    2 回复  |  直到 6 年前
        1
  •  5
  •   Stephan Dollberg    6 年前

    在move构造函数/赋值运算符中,需要使moved from对象无效。析构函数仍将对从对象移动的调用。如果它们的套接字不是0,那么析构函数仍将调用fd上的close。

        2
  •  1
  •   n. m. could be an AI    6 年前

    移动操作会中断,因为它们会让两个对象引用同一个套接字。您需要将一个假(无效)套接字值放入moved from对象,并在析构函数中检查该值。