C ++ Предотвращение вызова деструктора при использовании emplace () на карте - PullRequest
0 голосов
/ 21 октября 2018

У меня есть класс, который управляет ресурсом (сетевой сокет).

Я написал класс ConnectionHandler, который обрабатывает сетевой сокет, созданный из вызова к accept().

* 1006.* Этот класс разработан с учетом RAII, когда вызывается accept(), возвращаемый сокет помещается в ConnectionHandler, когда он выходит из области видимости, деструктор закрывает сокет.

Я такжеотслеживать все мои открытые ConnectionHandler, сохраняя их на карте (сопоставляет адрес сокета (IP: порт) с 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;
};

И, наконец, вот кодэто создает 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)));
    }

    [...]
}

Эта функция выполняется в потоке, отдельном от основного потока, поток сохраняется в объекте Server и присоединяется в деструкторе объекта Server.

Ответы [ 2 ]

0 голосов
/ 21 октября 2018

Ваши операции перемещения нарушены, так как они оставляют вас с двумя объектами, ссылающимися на один и тот же сокет.Вам необходимо поместить фиктивное (недопустимое) значение сокета в объект move-from и проверить это в деструкторе.

0 голосов
/ 21 октября 2018

В вашем конструкторе перемещения / операторе присваивания вы должны сделать недействительными перемещенные объекты.Деструкторы по-прежнему будут вызываться при перемещении с объектов.Если их сокет не равен 0, деструктор все равно будет вызывать close на fd.

...