int listener = socket(AF_INET6, SOCK_STREAM, 0);
if (listener == -1) throw std::system_error(errno, std::system_category());
try {
if (listen(listener, 1)) throw std::system_error(errno, std::system_category());
sockaddr_in6 listeningAddress;
socklen_t listeningAddressLength = sizeof(listeningAddress);
if (getsockname(listener, reinterpret_cast<sockaddr*>(&listeningAddress),
&listeningAddressLength))
throw std::system_error(errno, std::system_category());
char listeningAddressString[INET6_ADDRSTRLEN];
if (!inet_ntop(AF_INET6, &listeningAddress.sin6_addr, listeningAddressString,
sizeof(listeningAddressString)))
throw std::system_error(errno, std::system_category());
int listener = socket(AF_INET6, SOCK_STREAM, 0);
if (listener == -1) throw std::system_error(errno, std::system_category());
try {
if (listen(listener, 1)) throw std::system_error(errno, std::system_category());
sockaddr_in6 listeningAddress;
socklen_t listeningAddressLength = sizeof(listeningAddress);
if (getsockname(listener, reinterpret_cast<sockaddr*>(&listeningAddress),
&listeningAddressLength))
throw std::system_error(errno, std::system_category());
char listeningAddressString[INET6_ADDRSTRLEN];
if (!inet_ntop(AF_INET6, &listeningAddress.sin6_addr, listeningAddressString,
sizeof(listeningAddressString)))
throw std::system_error(errno, std::system_category());
}
}
本例,展示了如何在 C++ 程序中使用 C 标准网络库(如
、
等)并用 C++ 异常进行错误处理。
该代码段的功能是:
::1
);std::system_error
抛出异常。C 函数 | 作用 |
---|---|
socket(AF_INET6, SOCK_STREAM, 0) |
创建一个 IPv6 TCP socket |
listen(listener, 1) |
开始监听 socket |
getsockname(...) |
获取 socket 的本地绑定地址 |
inet_ntop(...) |
把二进制 IP 地址转为可读字符串 |
传统 C 的做法是检查返回值并手动处理 errno
。
在 C++ 中,你可以通过:
if (some_c_function() == -1)
throw std::system_error(errno, std::system_category());
这样你可以使用 try
/catch
语法来管理错误。
reinterpret_cast<sockaddr*>(&listeningAddress)
这是因为 getsockname
的参数要求的是 sockaddr*
,但我们使用的是 sockaddr_in6
类型,需要强制转换。
reinterpret_cast
来匹配参数;
来统一处理 errno 风格的错误;作者的观点强调经济成本高,总结为以下几点:
成本 | 原因 |
---|---|
设计成本高 | OO 框架通常需要复杂的抽象层次,设计难度大。 |
文档成本高 | 框架必须详细说明类、继承关系、接口等,写清楚很花时间。 |
学习成本高 | 初学者或新团队成员难以理解和掌握框架结构。 |
迁移/集成成本高 | 把旧代码适配到新框架可能涉及重构大量逻辑。 |
作者建议避免强制使用 OO 框架,尤其是在性能、维护、迭代成本敏感的项目中。相反,推荐使用:
理解如下:
Type for type, function for function, except for the exceptions.
在重新封装或绑定一个已有库时:
“类型对类型”:新接口中的类型设计应直接对应原库中的每个类型(例如,如果原库有结构体 Foo
,你也应该有一个封装后的 Foo
类型或类)。
“函数对函数”:每个原始函数应有一个等价的新函数,保持功能一致。
“除了异常(exceptions)”:唯一可以不直接映射的是异常机制,特别是跨语言或二进制边界时(因为异常处理机制可能不兼容)。在这种情况下,应使用错误码或返回结构代替异常。
如果原始 C++ 库有:
struct Widget {
std::string name;
};
int do_work(Widget w);
封装后的 C API 应为:
typedef struct widget* widget_t;
int do_work(widget_t w, error_t* out_error);
注意:不要直接暴露 std::string
,不要用 C++ 异常 —— 使用 error_t
等替代。
在为库设计接口(特别是封装 C 接口或编写适配层)时,应遵循以下命名规则,以保证一致性、清晰性和安全性:
Po7
),避免冲突,同时易记。unique_*
→ 表示拥有资源所有权的封装类型(如 unique_ptr
),暗示自动释放。*_cast
→ 表示是一个不安全或类型转换操作,提示使用时应谨慎(类似 C++ 中的 static_cast
、reinterpret_cast
等)。假设你在封装一个叫 libfoo
的 C 库,有函数:
FOO_HANDLE foo_create();
void foo_destroy(FOO_HANDLE h);
你在 C++ 中可设计为:
namespace po7 {
using handle = FOO_HANDLE;
unique_handle unique_create(); // 拥有资源,自动释放
void destroy(handle h); // 显式释放资源
int64_t unsafe_cast(handle h); // 明确表示是转换操作
}
int
, float
等),不必新建类型。using
声明把它们引入到你的命名空间,方便使用:using library::ExistingStruct;
unique_*
),实现 RAII,防止资源泄漏。int32_t
。struct Timestamp {
int64_t value;
// 重载加减等操作符
};
struct Point { float x, y; };
,直接using library::Point;
class unique_file {
FILE* fp;
// 析构时自动 fclose
};
理解!设计规则关于类型转换的总结如下:
包装(Wrap)
用 Wrap
把一个裸类型包进封装类型。
例如:Wrap
返回 Foo
类型的对象。
拆包(Unwrap)
用 Unwrap(wrapped)
从封装类型中取出裸类型。
例如:Unwrap(foo)
返回内部的裸指针或值。
夺取所有权(Seize)
用 Seize
从原始资源夺取所有权,封装成独占所有权类型。
例如:Seize
。
释放所有权(Release)
用 Release(seized)
将封装的所有权类型释放,得到裸资源。
例如:Release(unique_file)
返回 FILE*
。
Make(parameters...)
进行安全的显式构造或转换。xxx_cast
的函数。// 假设 Foo 是包装类型,FooRaw 是裸类型
Foo foo = Wrap<Foo>(foo_raw); // 包装
FooRaw raw = Unwrap(foo); // 拆包
unique_file uf = Seize<unique_file>(fptr); // 夺取所有权
FILE* fptr2 = Release(uf); // 释放所有权
MyStruct s = Make<MyStruct>(param1, param2); // 安全构造
// 不安全转换示例
Bar* bar = bar_cast(foo); // 明确表示转换有风险
设计参数规则的重点是:
设计返回值规则要点:
std::tuple
返回,顺序为:先库函数原始返回值,然后是所有输出参数按原顺序排列,方便解构和调用。auto listener = Po7::socket<Po7::af_inet6>(Po7::sock_stream, Po7::socket_protocol_t());
Po7::listen(*listener, 1);
std::cout << "Listening on “
<< Po7::Make<std::string>(Po7::getsockname(*listener)) << "\n";
auto acceptedTuple = Po7::accept(*listener);
auto& connectedSocket = std::get<0>(acceptedTuple);
auto& connectedAddress = std::get<1>(acceptedTuple);
std::cout << "Accepted a connection from “
<< Po7::Make<std::string>(connectedAddress) << "\n";
Po7::close(std::move(listener));
Po7::send(*connectedSocket, greeting);
char buffer[256];
while (std::size_t receivedLength = Po7::recv(*connectedSocket, buffer))
std::cout.write(buffer, static_cast<std::streamsize>(receivedLength));
Po7::close(std::move(connectedSocket));
auto listener = Po7::socket<Po7::af_inet6>(Po7::sock_stream, Po7::socket_protocol_t());
Po7::listen(*listener, 1);
std::cout << "Listening on " << Po7::Make<std::string>(Po7::getsockname(*listener)) << "\n";
auto acceptedTuple = Po7::accept(*listener);
auto& connectedSocket = std::get<0>(acceptedTuple);
auto& connectedAddress = std::get<1>(acceptedTuple);
std::cout << "Accepted a connection from " << Po7::Make<std::string>(connectedAddress) << "\n";
Po7::close(std::move(listener));
Po7::send(*connectedSocket, greeting);
char buffer[256];
while (std::size_t receivedLength = Po7::recv(*connectedSocket, buffer))
std::cout.write(buffer, static_cast<std::streamsize>(receivedLength));
Po7::close(std::move(connectedSocket));
当然,下面是你示例代码中用到的 Po7
命名空间对应的函数列表和简要说明:
// 创建套接字,使用协议族模板参数(如 af_inet6)
auto listener = Po7::socket<Po7::af_inet6>(Po7::sock_stream, Po7::socket_protocol_t());
// 监听套接字,传入引用,不用指针
Po7::listen(*listener, 1);
// 获取套接字绑定的地址信息,返回系统类型,使用 Make 转换成 std::string
auto address = Po7::getsockname(*listener);
auto addressStr = Po7::Make<std::string>(address);
// 接受一个连接,返回元组(套接字,地址)
auto acceptedTuple = Po7::accept(*listener);
// 关闭套接字,传递所有权(移动语义)
Po7::close(std::move(listener));
// 发送数据,传入引用套接字和数据
Po7::send(*connectedSocket, greeting);
// 接收数据,传入引用套接字和缓冲区,返回接收长度
auto receivedLength = Po7::recv(*connectedSocket, buffer);
// 关闭已连接套接字,传递所有权
Po7::close(std::move(connectedSocket));
总结:
函数名 | 功能描述 | 参数类型 | 返回类型 |
---|---|---|---|
Po7::socket |
创建套接字 | 模板参数协议族,协议类型 | 所有权类型(智能指针封装套接字) |
Po7::listen |
监听套接字 | 套接字引用,监听队列长度 | void ,异常抛出错误 |
Po7::getsockname |
获取套接字地址 | 套接字引用 | 套接字地址类型 |
Po7::Make |
类型转换,包装成标准类型 | 底层类型 | T (如 std::string ) |
Po7::accept |
接受连接,返回新套接字和地址的元组 | 套接字引用 | std::tuple<套接字类型, 地址类型> |
Po7::close |
关闭套接字,释放资源 | 套接字所有权类型(移动语义) | void ,异常抛出错误 |
Po7::send |
发送数据 | 套接字引用,数据缓冲区 | void ,异常抛出错误 |
Po7::recv |
接收数据 | 套接字引用,缓冲区指针 | 接收长度,零表示连接关闭 |
这些函数都遵守设计规则,比如:
std::tuple
。socket
API)自动化地封装起来。具体来说:PlusPlus::Boxed
以及 std::unique_ptr
(带自定义删除器)来管理底层资源(如套接字),防止资源泄漏。ThrowErrorFromErrno()
把系统调用的错误码转换成异常,统一错误处理逻辑。socket_domain_t
定义协议族,配合模板 socket_in_domain
,使得不同协议族的套接字类型区分明确。枚举封装
enum class socket_domain_t : int {};
template <> struct Wrapper<socket_domain_t> : PlusPlus::EnumWrapper<socket_domain_t> {};
const socket_domain_t af_inet = socket_domain_t(AF_INET);
// 其他协议族
用强类型枚举封装了 C 的协议族宏,避免魔法数字。
套接字类型定义和模板封装
struct SocketTag { ... };
using socket_t = PlusPlus::Boxed<SocketTag>;
template <socket_domain_t domain>
struct SocketInDomainTag { ... };
template <socket_domain_t domain>
using socket_in_domain = PlusPlus::Boxed<SocketInDomainTag<domain>>;
用 Boxed
类型包装底层整数(文件描述符),不同域用不同类型区分。
资源释放机制
struct SocketDeleter {
using pointer = PlusPlus::PointerToValue<socket_t>;
void operator()(pointer s) const; // 实现关闭套接字
};
using unique_socket = std::unique_ptr<const socket_t, SocketDeleter>;
template <socket_domain_t domain>
struct SocketInDomainDeleter {
using pointer = PlusPlus::PointerToValue<socket_in_domain<domain>>;
void operator()(pointer s) const { SocketDeleter()(s); }
operator SocketDeleter() const { return SocketDeleter(); }
};
template <socket_domain_t domain>
using unique_socket_in_domain = std::unique_ptr<const socket_in_domain<domain>, SocketInDomainDeleter<domain>>;
通过智能指针和自定义删除器自动关闭套接字,防止忘记关闭。
函数封装示例
unique_socket socket(socket_domain_t domain, socket_type_t type, socket_protocol_t protocol) {
return Invoke(Result<unique_socket>() + FailsWhenFalse(),
::socket,
In(domain, type, protocol),
ThrowErrorFromErrno());
}
template <socket_domain_t domain>
unique_socket_in_domain<domain> socket(socket_type_t type, socket_protocol_t protocol) {
return domain_cast<domain>(socket(domain, type, protocol));
}
Invoke
是自动调用底层 C 函数的模板,带有调用结果包装、错误检测和异常转换。这套设计: