探索一种C++中构造对象的方式

本文展示一种构造对象的方式,用户无需显式调用构造函数。
对于有参数的构造函数的类,该实现在构造改对象时传递默认值来构造。当然用户也可以指定(绑定)某个参数的值。 实现思路参考boost-ext/di的实现。
来看下例子:

struct Member{int x = 10;
};struct Member1 {int x = 11;
};class Example1{
public:Example1(Member x, Member1 x1) {std::cout << x.x << std::endl; // 10std::cout << x1.x << std::endl;  // 11}
};int main() {auto e1 = farrago::ObjectCreator<>().template Create<Example1>();
}

例子比较简单,构造一个ObjectCreator对象,并调用他的Create来创建一个Example1的对象,
因为使用ObjectCreator来构造,所以不需要传递参数,它会自动构造。
这样做的好处是,当你构造一个对象时,可以无需考虑这个对象的构造函数是几个参数或类型,当想要增加参数时则无需修改代码,当然指定参数的话除外。这种用法也被称为依赖注入

构思主体实现

看起来还蛮酷炫,那主要还是看如何做到的?
先来说下主体想法,首先最重要的当然是ObjectCreator这个类中如何知道要构造的对象的构造函数的参数类型是什么呢,知道参数类型才能构造一个参数传递,同时参数的也同样需要ObjectCreator来构造,依次递归下去。
上边说到了两个问题要解决,第一个就是如何识别构造函数的参数类型,第二个是针对构造函数参数也需要构造的情况下,如果递归构造?

识别构造函数参数类型

我们使用AnyType的形式来识别出来构造函数的参数,举个简单的例子:

struct AnyType {template<typename T>operator T() {return T{};}
};struct Member {};struct Example {Example(Member m, int) {}
};int main() {Example(AnyType(), 2);return 0;
}

通过调用AnyType()可以匹配至任意类型,然后在构造Example编译器会去找相应的类型来构造。
大家可能发现我使用的是多个参数来举例AnyType,如果参数是一个使用AnyType会有冲突,因为拷贝构造函数也是一个参数,所以编译器会识别冲突,这个问题我们后边也是需要处理的。

class Example {
public:Example(Member m) {std::cout << m.x << std::endl;}
};int main() {Example e(AnyType{});return 0;
}// -------- 以下报错
note: candidate: 'Example::Example(Member)'
|     Example(Member m) {
|     ^~~~~~~
: note: candidate: 'constexpr Example::Example(const Example&)'
class Example {

递归构造构造函数的参数

因为构造函数的参数可能是一个类对象,这个对象的构造函数参数又是其他类对象,我们识别类型后继续调用函数来构造这个对象,以此类推。

保存绑定参数

当然使用过程也不全部是使用默认构造,可能也需要传递特定参数与构造函数的参数进行绑定,但是构造函数的参数类型又是多样的。这里我采用了tuple先来保存,倘若识别出来的类型和保存的数据类型是一致的,则不去构造而是直接传递该数据给构造函数。

代码实现

那沿着上边的思路就开始写代码,肯定有一个AnyType的类及Objectcreator的类。ObjectCreator用来构造对象返回,会只用AnyType类来识别类型。

ObjectCreator

大概看下具体的实现:

template<typename... Args>
class ObjectCreator {
public:template<typename... Ts>explicit ObjectCreator(Ts&&... args) : dependency_(std::forward<Ts>(args)...) {}// ...private:std::tuple<const Args& ...> dependency_;
};

我们使用tuple保存要绑定的参数时,数据的保存就得进行拷贝,我们这里为了避免拷贝,tuple中的类型是const左引用,这样就得用户自己来维护要绑定的参数的生命周期。
Args是要绑定的参数类型,构造函数中为了避免拷贝使用完美转发来实现。dependency_就是保存绑定参数的数据结构

template<typename... Args>
class ObjectCreator {
// ...template<typename T>
T Create() {if constexpr ((std::is_same<T, Args>::value || ...)) {return std::get<const T&>(dependency_);}else if constexpr (std::is_default_constructible_v<T>) {return T{};}else if constexpr (std::is_constructible<T, AnyFirstRefType<ObjectCreator, T, FarragoNull, Args...>>::value) {return T{AnyFirstRefType<ObjectCreator, T, FarragoNull, Args...>{this}};}else if constexpr (std::is_constructible<T, AnyFirstType<ObjectCreator, T, FarragoNull, Args...>>::value) {return T{AnyFirstType<ObjectCreator, T, FarragoNull, Args...>{this}};}else {return CreateMoreParamObject<T>(std::make_index_sequence<10>{});}
}// ...
};

这里就是create函数了:

  • 首先判断是不是要创建的类对象已经绑定了,如果绑定了则直接从tuple中取出返回。
  • 没有绑定的话然后再判断默认构造(即可以无参构造)是否可以构造,可以的话返回一个空对象。
  • 然后进行判断是不是一个参数构造函数的判断,一个参数这里分成了两种,是引用类型或者非引用类型。这样做是因为,T和T&在识别是会冲突,所以分开处理。举例说明:
struct AnyType {template<typename T>operator T() {return T{};}template<typename T>operator T&() {return T{};}
};class Example {
public:Example(Member m, int) {std::cout << m.x << std::endl;}
};Example e(AnyType{}, 7);// 报错如下:
error: conversion from 'AnyType' to 'Member' is ambiguous
Example e(AnyType{}, 7);
^~~~~~~~~
candidate: 'AnyType::operator T() [with T = Member]'
operator T() {
^~~~~~~~
note: candidate: 'AnyType::operator T&() [with T = Member]'
operator T&() {
  • 最后是多个参数的构造函数进行构造,一个参数和多个参数分开的原因是,一个参数需要对拷贝构造函数及单参的构造函数冲突的情况进行处理,我们传递了1~10的整数序列作为参数给CreateMoreParamObject函数,这里表示目前该实现最多只能支持10个参数的构造函数。

继续看下多参的构造:

template<typename T, std::size_t... Ns>
T CreateMoreParamObject(const std::index_sequence<Ns...>&) {if constexpr (std::is_constructible_v<T, At<AnyRefType<ObjectCreator, FarragoNull, Args...>, Ns>...>) {return T{At<AnyRefType<ObjectCreator, FarragoNull, Args...>, Ns>{this}...};}else {return CreateMoreParamObject<T>(std::make_index_sequence<sizeof...(Ns) - 1>{});}
}

首先判断是否可以由多个AnyRefType类型来构造出来,如果可以的话,直接构造对象,不可以的话就需要将参数个数减少重新匹配。

AnyType

然后我们来观察AnyType如何编写,先来看下AnyFirstType的情况。
为了避免和拷贝构造函数冲突,简单做一下优化:

struct AnyFirstType {template <typename T,typename = std::enable_if_t<!std::is_same_v<Src, T>>>constexpr operator T() {return creator_->template Create<T>();}};

我们使用SFINAE来将拷贝构造函数排除在外,使用AnyFirstType识别时参数类型时,需要将要构造的类当作模版参数传递给Src,让T与Src不一样进而告诉编译器要调用的不是拷贝构造函数而是其他的函数。
creator_就是ObjectCreator对象,对参数的构造对Create函数进行递归调用。
多个参数也是类似实现,只是不需要额外判断是不是拷贝构造函数的参数。
不过还有一个点可能需要注意就是,如果构造函数的类型是引用类型,在和绑定参数匹配情况下会多一次拷贝,所以我们也还是区分开来。

template <typename Creator, typename Src, typename... Args>
struct AnyFirstRefType {template <typename T, typename = std::enable_if_t<!std::is_same_v<Src, std::decay_t<T>>>,typename = std::enable_if_t<(std::is_same<std::decay_t<T>, Args>::value || ...)>>constexpr operator T& () {return const_cast<T&>(creator_->template GetDependency<T>());}template <typename T, typename = std::enable_if_t<!std::is_same_v<Src, std::decay_t<T>>>,typename = std::enable_if_t<(std::is_same<std::decay_t<T>, Args>::value || ...)>>constexpr operator T &&() {return static_cast<T&&>(const_cast<T&>(creator_->template GetDependency<T>()));}Creator* creator_ = nullptr;
};

在和绑定参数匹配并且传递引用的情况下,我们单独实现,直接返回不再调用Creator的Create函数,并且做一下强制转化。多参数的类型识别也是类似。

总结

本文展示了一种对象构造的实现,使用AnyType的思路实现,中间也处理很多的问题。对于无需绑定(或部分绑定)构造函数参数的对象的构造,可扩展性及可维护性都有很好提升。当然该实现目前也尚不完备,目前只是类型绑定,也可以实现参数名字绑定等功能。
上边论述的代码我放到了 https://github.com/leap-ticking/farrago 位置,欢迎取用。

ref

  • https://github.com/boost-ext/di
  • https://github.com/leap-ticking/farrago

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/94751.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

milvus 结合Thowee 文本转向量 ,新建表,存储,搜索,删除

1.向量数据库科普 【上集】向量数据库技术鉴赏 【下集】向量数据库技术鉴赏 milvus连接 from pymilvus import connections, FieldSchema, CollectionSchema, DataType, Collection, utility connections.connect(host124.****, port19530)2.milvus Thowee 文本转向量 使用 …

Ae 效果:CC Page Turn

扭曲/CC Page Turn Distort/CC Page Turn CC Page Turn &#xff08;CC 翻页&#xff09;主要用于模拟书页翻动的效果。通过使用该效果&#xff0c;用户可以创建出像书页或杂志页面翻动的视觉效果&#xff0c;增强影片的交互性和视觉吸引力。 ◆ ◆ ◆ 效果属性说明 Contro…

opencv入门教程

opencv入门教程 图像的读取&#xff0c;显示&#xff0c;与写入摄像头保存视频读取视频画画鼠标操作event 参数说明flags 参数说明 轨迹栏图像基本操作图像处理HSV颜色空间几何变换放大缩小平移旋转仿射变换透视变换简单阈值自适应阈值Otsu的二值化 2D卷积&#xff08;图像过滤…

Jmeter-Beanshell取样器中引入自制的java脚本(jar java class)

1、内置变量&#xff1a;log&#xff1a;写入信息到jmeter.log&#xff0c;使用方法&#xff1a; log.info(“hello,world”)&#xff0c;也可以在jmetergui上看到打印的信息。 2、设置Jmeter变量的值&#xff0c;将定义的变量或提取的变量做修改后再进行传参 Vars:操作jmete…

创建一个新的IDEA插件项目

启动IntelliJ IDEA并按照以下步骤创建新的插件项目&#xff1a; 打开IntelliJ IDEA并单击“Create New Project”&#xff08;创建新项目&#xff09;。 在左侧菜单栏中选择“IntelliJ Platform Plugin”&#xff08;IntelliJ平台插件&#xff09;。 在右侧窗格中&#xff0c…

Android base64编码、图片转换

1 将base64编码转化成图片 &#xff08;1&#xff09;类似base64流的图片解析并展示&#xff1a; data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAAUDBAQEAwUEBAQFBQUGBwwIBwcH Bw8LCwkMEQ8SEhEPERETFhwXExQaFRERGCEYGh0dHx8fExciJCIeJBweHx7/2wBDAQUFBQcGBw4ICA4…

Python:操作SQLite数据库简单示例

本文用最简单的示例演示python标准库提供的SQLite数据库进行新增、查询数据的过程。 代码文件app.py # -*- coding: UTF-8 -*- from flask import Flask import sqlite3app Flask(__name__)app.route(/) def hello_world():return Hello World!#创建数据库 app.route(/creat…

SpringMVC的数据绑定

一、前言 SpringMVC的数据绑定是指将HTTP请求参数绑定到Java对象上。这样可以方便地从请求中获取数据并将其传递给业务逻辑。在SpringMVC中&#xff0c;可以使用RequestParam和ModelAttribute等注解来实现数据绑定。 二、使用RequestParam注解 RequestParam注解用于将请求参…

ros编译报错-- Could NOT find ros_ethercat_eml (missing: ros_ethercat_eml_DIR)

– Could NOT find ros_ethercat_eml (missing: ros_ethercat_eml_DIR) – Could not find the required component ‘ros_ethercat_eml’. The following CMake error indicates that you either need to install the package with the same name or change your environment …

linux入门---信号的保存和捕捉

目录标题 信号的一些概念信号的保存pending表block表handler表 信号的捕捉内核态和用户态信号的捕捉 信号的一些概念 1.进程会收到各种各样的信号&#xff0c;那么程序对该信号进行实际处理的动作叫做信号的递达。 2.我们之前说过当进程收到信号的时候可能并不会立即处理这个信…

虚拟机通过nat模式端口映射实现内网穿透

虚拟机通过nat模式端口映射实现内网穿透 1.网络状态 windows虚拟主机的IP为局域网的私有IP192.168.1.7linux的虚拟主机IP为nat的172.36.4.1062.linux修改nat模式的端口映射 3.windows宿主机防火墙添加规则,&#xff08;或者直接关闭公共网络防火墙&#xff0c;不安全&#xf…

Appleid苹果账号自动解锁改密(自动解锁二验改密码)

目前该项目能实现以下功能&#xff1a; 多用户使用&#xff0c;权限控制多账号管理账号分享页&#xff0c;支持设置密码、有效期、自定义HTML内容自动解锁与关闭二步验证自动/定时修改密码自动删除Apple ID中的设备代理池与Selenium集群&#xff0c;提高解锁成功率允许手动触发…

LogisticRegression 与 LogisticRegressionCV 的区别

LogisticRegression 和 LogisticRegressionCV 是 scikit-learn 库中用于逻辑回归的两个类&#xff0c;它们之间的区别如下。 1、LogisticRegression LogisticRegression 是用于二分类或多分类问题的逻辑回归模型。可以使用不同的优化算法&#xff08;如拟牛顿法、坐标下降法&…

C++ 结构化、联合、枚举、

结构化 #include <iostream> int main() { // 结构1struct contact {char phone[20];char email[20];}; // 这里可以添加变量 也可以在后面自行创建// 结构2 注意嵌套struct person {char name[20];int gender;double h;double w;struct contact c;};// 创建 并 赋值…

国庆作业5

QT实现TCP服务器客户端的搭建 服务器代码&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);server new QTcpServer(this);connect(server,&Q…

新手学习笔记-----⽂件操作

目录 1. 为什么使⽤⽂件&#xff1f; 2. 什么是⽂件&#xff1f; 2.1 程序⽂件 2.2 数据⽂件 2.3 ⽂件名 3. ⼆进制⽂件和⽂本⽂件&#xff1f; 4. ⽂件的打开和关闭 4.1 流和标准流 4.1.1 流 4.1.2 标准流 4.2 ⽂件指针 4.3 ⽂件的打开和关闭 5. ⽂件的顺序读写 …

学信息系统项目管理师第4版系列17_干系人管理

1. 项目经理和团队管理干系人的能力决定着项目的成败 2. 干系人满意度应作为项目目标加以识别和管理 3. 发展趋势和新兴实践 3.1. 识别所有干系人&#xff0c;而非在限定范围内 3.2. 确保所有团队成员都涉及引导干系人参与的活 3.3. 定期审查干系人群体&#xff0c;可与单…

全志ARM926 Melis2.0系统的开发指引④

全志ARM926 Melis2.0系统的开发指引④ 编写目的7. 固件打包脚本7.1.概要描述7.2.术语定义7.2.1. makefile7.2.2. image.bat 7.3.工具介绍7.4.打包步骤7.4.1. makefile 部分7.4.2. image.bat 部分 7.5.问题与解决方案7.5.1. 固件由那些文件构成7.5.2. melis100.fex 文件包含什么…

【动态规划】96. 不同的二叉搜索树

96. 不同的二叉搜索树 解题思路 base case dp[0] 1 一个空节点 也是一颗二叉树状态&#xff1a;dp[i] 对于每一个节点i 作为根节点 那么它的二叉搜索树的数量有多少外层循环&#xff1a;遍历所有的可能节点数目 内存循环遍历所有左右子树组合情况 class Solution {public i…

Greenplum7一键安装

2023年9月底&#xff0c;Greenplum 发布了7.0.0版本&#xff0c;并于2023年10月03日开放了安装部署说明文档&#xff0c;现在快速尝鲜版的docker一键部署方式如下&#xff1a; mkdir /data/gpdb docker run -d --name greenplum -p 15432:5432 -v /data/gpdb:/data inrgihc/g…