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



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

我编写了一个类ConnectionHandler,它处理通过调用accept()创建的网络套接字。

此类在设计时考虑了 RAII,当调用accept()时,返回的套接字被放入ConnectionHandler中,当这超出范围时,析构函数关闭套接字。

我还通过将它们保存在映射中来跟踪我所有打开的ConnectionHandler(将套接字地址 (IP:Port( 映射到与该地址对应的ConnectionHandler(。

不过,我在将这些ConnectionHandler"嵌入"到地图中时遇到了问题。

我已经做到了不能复制ConnectionHandler(至少我相信我已经做到了(,但是当调用std::map::emplace时,ConnectionHandler的析构函数被调用(大概是为了删除沿行某处创建的临时对象(并且套接字被关闭。

如您所见,这会产生一个问题,因为现在套接字无法在程序的下游使用。

我有什么办法可以防止ConnectionHandler的析构函数在放入std::map时被调用?

这是ConnectionHandler的代码: 头文件:

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;
};

最后,下面是创建连接处理程序并将其放置在映射中的代码:

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)));
}
[...]
}

此函数在独立于主线程的线程上运行,线程保存在服务器对象中,并联接在服务器对象的析构函数中。

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

您的移动操作被破坏,因为它们会给您留下两个引用同一套接字的对象。您需要将虚拟(无效(套接字值放入移自对象,并在析构函数中检查该值。

相关内容

  • 没有找到相关文章

最新更新