最近看到一个NamedType的开源库,被里面的Strong Type这个概念和里面的模版实现给秀了一脸,特此总结学习一下
GitHub - joboccara/NamedType: Implementation of strong types in C++
C++本身是一种强类型语言,类型包括int、double等这些build in类型 以及class类型,强类型的意思是所有变量的类型在编译阶段应该都是确定的。上面提到的Strong Type并不是这个意思,在C++中,Strong Type(强类型)是一种编程技巧,是对C++的类型进行加强的意思。
它用于定义一种类型安全的机制,使得编译器能够区分语义相似但实际意义不同的类型,从而避免因类型混淆导致的潜在错误。这种技术常见于处理需要区分类似数据的场景,比如标识符、单位、或封装基本数据类型以增强可读性和安全性。Strong Type在某种程度上和C++的自定义字面量有点相似,本文也对他们进行一些对比。
强类型的基本思想
强类型通常是通过封装一个原始类型(如 int
, double
等)实现的,我们可以先看如下示例来感受一下用法
通过简单封装赋予语义
struct Meters {
double value;
};
struct Seconds {
double value;
};
Meters operator+(Meters lhs, Meters rhs) {
return Meters{lhs.value + rhs.value};
}
// 用法
Meters distance1{5.0};
Meters distance2{10.0};
Meters total = distance1 + distance2; // OK
// Seconds time = distance1; // 编译错误,类型不兼容
通过模板实现通用强类型
template <typename Tag, typename T>
class StrongType {
public:
explicit StrongType(T value) : value_(value) {}
T get() const { return value_; }
private:
T value_;
};
// 标识语义的标签
struct UserIdTag {};
struct ProductIdTag {};
using UserId = StrongType<UserIdTag, int>;
using ProductId = StrongType<ProductIdTag, int>;
UserId uid(42);
ProductId pid(101);
// pid = uid; // 编译错误,类型不兼容
强类型与操作符重载
如果需要增强强类型的可用性,可以为其定义操作符:
template <typename Tag, typename T>
class StrongType {
public:
explicit StrongType(T value) : value_(value) {}
T get() const { return value_; }
// 支持操作符重载
StrongType operator+(const StrongType& other) const {
return StrongType(value_ + other.value_);
}
private:
T value_;
};
struct DistanceTag {};
using Distance = StrongType<DistanceTag, double>;
// 用法
Distance d1(5.0), d2(10.0);
Distance total = d1 + d2; // OK
应用场景
- 标识符区分: 避免混淆例如用户 ID 和产品 ID 等数据。
- 物理量单位: 避免直接使用
double
等基础类型混淆不同单位的数值(如米和秒)。 - 类型安全接口: 增强类型安全,避免误用相似类型的数据。
- 业务逻辑抽象: 为关键业务对象创建显式的类型,增强代码可读性和可维护性。
Strong Type 和 C++字面量
上面Strong Type的思想其实和C++字面量有异曲同工之妙,我们不妨也来总结一下C+++自定义字面量的知识。
用户自定义字面量是 C++ 提供的扩展功能,通过为特定的字面量(如整型、浮点型、字符串等)自定义行为,可以实现特定语义,例如单位转换、数据封装等。自定义字面量通常使用 operator""
实现。
基本语法
用户自定义字面量是通过重载 operator""
实现的,后面可以接字面量标识符。根据传入的参数类型,用户自定义字面量分为以下几种形式:
参数类型 | 示例 | 常用场景 |
|
| 处理字符串类型 |
|
| 处理整型字面量 |
|
| 处理浮点型字面量 |
|
| 处理字符字面量 |
|
| 处理字符串并获得长度 |
自定义字面量实现
以下是针对不同类型的自定义字面量的实现方式:
整型字面量
#include <iostream>
// 自定义字面量,用于单位转换
constexpr unsigned long long operator"" _kg(unsigned long long value) {
return value * 1000; // 转换为克
}
int main() {
auto weight = 5_kg; // 表示 5 公斤
std::cout << "Weight in grams: " << weight << " g\n"; // 输出 5000 g
return 0;
}
浮点型字面量
#include <iostream>
// 自定义字面量,用于单位转换
constexpr long double operator"" _m(long double value) {
return value * 100; // 转换为厘米
}
int main() {
auto length = 2.5_m; // 表示 2.5 米
std::cout << "Length in cm: " << length << " cm\n"; // 输出 250 cm
return 0;
}
字符串字面量
#include <iostream>
#include <string>
// 自定义字面量,处理字符串
std::string operator"" _hello(const char* str, size_t) {
return std::string("Hello, ") + str;
}
int main() {
auto greeting = "World"_hello; // 添加 "Hello, "
std::cout << greeting << std::endl; // 输出 "Hello, World"
return 0;
}
字符串和长度字面量
C++20 前,可以通过传递字符串和长度参数实现更灵活的功能:
#include <iostream>
// 自定义字面量,打印字符串和长度
void operator"" _log(const char* str, size_t len) {
std::cout << "String: " << str << ", Length: " << len << std::endl;
}
int main() {
"Hello World"_log; // 输出 String: Hello World, Length: 11
return 0;
}
C++自定义字面量也可以给变量赋予直接的语义信息,但是功能比较单一,而Strong Type通过类的封装,可以实现更复杂的功能。
Strong Type 和 C++字面量结合
#include <iostream>
// 定义强类型 Distance
struct Distance {
explicit Distance(double meters) : meters_(meters) {}
double getMeters() const { return meters_; }
private:
double meters_;
};
// 自定义字面量
constexpr Distance operator"" _km(long double value) {
return Distance(static_cast<double>(value * 1000.0));
}
int main() {
Distance d = 1.5_km; // 使用字面量创建强类型对象
std::cout << "Distance: " << d.getMeters() << " meters" << std::endl; // 输出 1500 meters
return 0;
}
总结
据说美国航空航天局火星气候探测者号的失联事故是因为代码中两种测量系统不匹配导致的(https://en.wikipedia.org/wiki/Mars_Climate_Orbiter), 如果能采取上面的编程技术,或许能避免这种事故的产生,当然这是后话。
下面总结一下上面编程技巧:
优点:
- 提高类型安全性。
- 增强代码可读性。
- 防止逻辑错误。
缺点:
- 增加一定的代码复杂性。
- 性能开销(通常可以忽略)。
我将在下一篇中对上面的NamedType源码进行一些解读。
标签:字面,自定义,double,编程,value,StrongType,C++,类型 From: https://blog.csdn.net/leizhengshenglzs/article/details/144162205