Как настроить стандартный контейнер с классом RAII?

Чтобы реализовать идиому raii для типа SOCKET, я создали обертку. Оболочка вызывает connect в конструкторе и closesocket в своем деструкторе. std::map содержит все используемые сокеты. К сожалению, вставка нового сокета в контейнер вызывает деструктор временного и, по сути, закрывает только что открытый сокет. Есть ли общий способ преодолеть это?

Вот код:

#include <iostream>
#include <stdexcept>
#include <map>
#include <winsock2.h>

struct Socket {
    SOCKET mSock;
    Socket() : mSock(INVALID_SOCKET) {}
    Socket(std::string ip, int port);
    ~Socket();
};

Socket::Socket(std::string ip, int port) {
    mSock = socket(AF_INET, SOCK_STREAM, 0);
    if (mSock == INVALID_SOCKET)
        throw std::runtime_error("socket()");
    SOCKADDR_IN addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    if (connect(mSock, reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr))
        == SOCKET_ERROR)
        throw std::runtime_error("connect()");
    std::cout << mSock << " connected" << std::endl;
}

Socket::~Socket() {
    if (mSock != INVALID_SOCKET) {
        closesocket(mSock);
        std::cout << mSock << " closed" << std::endl;
    }
}

int main() {
    WSADATA wsa;
    WSAStartup(MAKEWORD(2, 0), &wsa);
    std::map<int, Socket> outbound;
    // calls constructur but also destructor
    outbound[0] = Socket("192.168.128.125", 4023);
    WSACleanup();
    return 0;
}

И вывод:

1952 connected
1952 closed
1952 closed

person Christian Ammer    schedule 25.07.2013    source источник
comment
Используйте семантику перемещения и удалите конструктор/назначение копии Socket. Если подумать, нет смысла копировать сокет.   -  person syam    schedule 25.07.2013
comment
Прежде всего, вы должны сделать его некопируемым, хотя он должен быть перемещаемым.   -  person Nawaz    schedule 25.07.2013
comment
@syam: я не могу использовать семантику перемещения, потому что: я пытался преобразовать этот проект VC6 в проект Visual Studio 2010, но это было невозможно из-за зависимостей от связанных библиотек VC6.   -  person Christian Ammer    schedule 25.07.2013
comment
Я считаю, что вы также можете сделать что-то вроде outbound[0].connect("192.168.128.125", ...), если бы был реализован метод connect. И, как сказал syam, сделайте конструктор копирования/оператор присваивания закрытым, чтобы предотвратить замеченную вами ошибку.   -  person Rollie    schedule 25.07.2013
comment
@ChristianAmmer А, хорошо. Тогда храните не сам объект, а указатель/ссылку на него (как сказал Юрий, умные указатели - хороший выбор). Тем не менее, сделайте сделайте свой класс некопируемым, чтобы вы могли обнаруживать такие проблемы во время компиляции.   -  person syam    schedule 25.07.2013
comment
@Rollie: Ваше предложение имеет для меня смысл. Если вы превратите свой комментарий в ответ, я проголосую за него.   -  person Christian Ammer    schedule 25.07.2013


Ответы (2)


Охранники ресурсов могут храниться в контейнерах STL, если вы поместите их в смарт-указатель. Какой именно интеллектуальный указатель использовать, зависит от вашей среды, я бы рекомендовал использовать boost::shared_ptr в качестве отправной точки, если вы не уверены.

Следуя этому пути, вы будете соответствовать как семантике защиты ресурсов (только один экземпляр, который управляет временем жизни ресурса), так и контейнеру STL (сохраните эквивалентную копию переданного элемента).

person Yury Schkatula    schedule 25.07.2013

Как уже упоминалось, вы должны предотвратить копирование вашего объекта Socket, что можно сделать, добавив:

private:
  Socket(const Socket &){}
  Socket & operator=(const Socket &){return *this;}

Затем, чтобы на самом деле подключиться, вы можете сделать это после построения по умолчанию с реализацией метода подключения, который может быть вызван явно или оставшимся конструктором:

bool Socket::connect(std::string ip, int port) {
    mSock = socket(AF_INET, SOCK_STREAM, 0);
    if (mSock == INVALID_SOCKET)
        return false;
    SOCKADDR_IN addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());
    if (connect(mSock, reinterpret_cast<SOCKADDR*>(&addr), sizeof(addr))
        == SOCKET_ERROR)
        return false;
    std::cout << mSock << " connected" << std::endl;
    return true;
}

Socket::Socket(std::string ip, int port) {
    if (!connect(ip, port))
        throw std::runtime_error("socket()");
}
person Rollie    schedule 25.07.2013
comment
Я очень надеялся, что это работает, но строка outbound[0].init("192.168.128.125", 4023) приводит к ошибке C2248 (не удается получить доступ к члену "доступа"...). Мне казалось, что некопируемые элементы нельзя вставлять на карту, и это действительно причина, поскольку позже я нашел объяснение в этом ответ. - person Christian Ammer; 26.07.2013