首页 > 编程语言 >C++ 编程技巧之StrongType(1)

C++ 编程技巧之StrongType(1)

时间:2024-11-30 22:59:15浏览次数:8  
标签:字面 自定义 double 编程 value StrongType C++ 类型

最近看到一个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"" 实现的,后面可以接字面量标识符。根据传入的参数类型,用户自定义字面量分为以下几种形式:

参数类型

示例

常用场景

const char*

"example"_id

处理字符串类型

unsigned long long

42_kg

处理整型字面量

long double

3.14_m

处理浮点型字面量

char

'c'_symbol

处理字符字面量

const char*, size_t

"hello"_custom

处理字符串并获得长度

自定义字面量实现

以下是针对不同类型的自定义字面量的实现方式:

整型字面量
#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

相关文章

  • Shell编程 - 括号篇
    ()用途1:在运算中,先计算小括号里面的内容用途2:数组用途3:匹配分组(())用途1:表达式,不支持-eq这类的运算符。不支持-a和-o,支持<=、>=、<、>这类比较符和&&、||用途2:C语言风格的for(())表达式$()执行Shell命令,与反撇号等效$(())用途1:简单算数运算用途2:支......
  • Shell编程 - 函数篇
    自建函数库-颜色字符串颜色字符串输出颜色,有时候关键地方需要醒目,颜色是最好的方式:字体颜色字体背景颜色显示方式30:黑40:黑0:终端默认设置31:红41:深红1:高亮显示32:绿42:绿4:下划线33:黄43:黄色5:闪烁34:蓝色44:蓝色7:反白显示35:紫色45:紫色8:隐藏......
  • Shell编程 - 引号篇
    双引号、单引号、不加引号与反引号在变量赋值时,如果值有空格,Shell会把空格后面的字符串解释为命令:#VAR=123-bash:2:commandnotfound#VAR="123"#echo$VAR123#VAR='123'#echo$VAR123看不出什么区别,再举个说明:#N=3#VAR="12$N"#echo$VAR12......
  • 第十六届蓝桥杯模拟赛(第二期)c++答案与代码
    一、【问题描述】如果一个数p是个质数,同时又是整数a的约数,则p称为a的一个质因数。请问,2024的最大的质因数是多少?答案:23#include<bits/stdc++.h>usingnamespacestd;usingll=longlong;intmain(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);......
  • B4X编程语言:B4X控件的尺寸大小属性(宽度/高度属性)
            B4X控件的尺寸大小属性(宽度/高度属性)是指Width /Height属性(B4J中还有PrefWidth /PrefHeight属性)。        1、Width        设置或获取控件的宽度。        用法示例:        设置控件Label1的宽度:Label1.Width=200......
  • 树莓派上手攻略:轻松切换至默认Python 3环境,解锁更多编程可能
    树莓派上手攻略:轻松切换至默认Python3环境,解锁更多编程可能前言树莓派作为一款开源的微型计算机,因其低成本和高灵活性而受到广泛欢迎。Python作为树莓派的官方编程语言,以其简洁的语法和强大的功能,成为了许多初学者和开发者的首选。然而,默认情况下,树莓派可能预装了Python2,这......
  • C++中static初始化一次的真实含义
    1. static 变量的初始化与赋值static 变量在C++中有一个特别的性质:它们在程序的生命周期内只会被初始化一次,但之后可以继续对其进行赋值。初始化:static 变量在程序的初始化阶段(即程序第一次执行时)会被初始化一次。如果是局部 static 变量,则它会在第一次执行到该变量所......
  • 移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.C++11(2)
    1.可变模板参数C++11引入的可变模板参数(variadictemplates)使得模板参数的数量可以是任意多个,极大地提升了C++的模板编程能力。以下是C++11中可变模板参数的详细总结:1.基本语法在模板参数列表中,通过...(三个点)来表示可变参数。常见的使用形式如下:template<typena......
  • Python入门 - 编程基础
    代码与文本No.1输入和输出概念:输入:计算机【收到】来自外界的数据传递·输出:计算机对外界【发出】的数据传递print:理解为“打印”,能让计算机在屏幕上输出内容语法:print("Helloworld")print语法包含四个部分:指令名print;指令附带的括号();标记文本的双引号”;引用的文本......
  • 读书笔记:C++程序设计原理与实践(基础篇)之八
    “程序员的工作不仅是写代码,而是解决问题。”                                        ——SteveMcConnell总阅读时间约为 5~10分钟。前言在本系列第二篇博文中【读书笔记:C++程序设计原理与实践(基础篇)之二-CSDN博客】,我们......