【问题标题】:How to set up a std container with a RAII class?如何使用 RAII 类设置标准容器?
【发布时间】:2013-07-25 17:02:43
【问题描述】:

为了实现SOCKET 类型的 习惯用法,我创建了一个包装器。包装器在构造函数中调用connect,在其析构函数中调用closesocketstd::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

【问题讨论】:

  • 使用移动语义并删除Socket的复制构造器/赋值。仔细想想,复制一个socket是没有意义的。
  • 首先,您必须使其不可复制,尽管它应该是可移动的
  • @syam:我不能使用移动语义,因为:我尝试将这个 VC6 项目转换为 Visual Studio 2010 项目,但由于依赖于链接的 VC6 库,这是不可能的。跨度>
  • 如果实现了connect 方法,我相信您也可以执行outbound[0].connect("192.168.128.125", ...) 之类的操作。正如 syam 所说,将复制构造函数/赋值运算符设为私有以防止您注意到的错误。
  • @ChristianAmmer 好的。然后不要存储对象本身,而是对它的指针/引用(正如 Yury 所说,智能指针是一个不错的选择)。尽管如此,do 使您的类不可复制,以便您可以在编译时检测到此类问题。

标签: raii c++ raii


【解决方案1】:

如果您将资源保护器包装在智能指针中,则它们可以存储在 STL 容器中。具体使用哪个智能指针取决于您的环境,如果您不确定,我建议您以 boost::shared_ptr 为起点。

按照这种方式,您将符合资源保护语义(只有一个管理资源生命周期的实例)和 STL 容器(保留传递项目的等效副本)。

【讨论】:

    【解决方案2】:

    正如其他人所提到的,您应该防止复制您的 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()");
    }
    

    【讨论】:

    • 我很有希望,这行得通,但outbound[0].init("192.168.128.125", 4023) 行会导致错误 C2248(无法访问“访问”成员...)。在我看来,不可复制的项目无法插入到地图中,这就是我后来在this answer 中找到解释的原因。
    猜你喜欢
    • 1970-01-01
    • 2014-08-30
    • 2013-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多