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

这篇具有很好参考价值的文章主要介绍了探索一种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; // 10
        std::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 位置,欢迎取用。文章来源地址https://www.toymoban.com/news/detail-728114.html

ref

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

到了这里,关于探索一种C++中构造对象的方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】类与对象(构造函数、析构函数、拷贝构造函数、常引用)

       🌈个人主页: 秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343 🔥 系列专栏:   目录 类的6个默认成员函数 构造函数 特性  析构函数 特性  析构的顺序 拷贝构造函数 特性 常引用      💬 hello! 各位铁子们大家好哇。              今日更新了类与对象的构造函数、

    2024年02月21日
    浏览(46)
  • C++ 对象生成:构造函数

    C++编译器提供了构造函数供程序生成对象 这是一个与类同名的函数,参数可以有多种形式(重载) 没有返回类型声明 一般情况下,编译器自动调用 还有析构函数 构造函数可以为对象的构建进行初始化 C++编译器在类内没有构造函数时会生成一个无参构造函数 推荐一个零声学

    2024年02月12日
    浏览(36)
  • 【C++初阶】四、类和对象(构造函数、析构函数、拷贝构造函数、赋值运算符重载函数)

    ========================================================================= 相关代码gitee自取 : C语言学习日记: 加油努力 (gitee.com)  ========================================================================= 接上期 : 【C++初阶】三、类和对象 (面向过程、class类、类的访问限定符和封装、类的实例化、类对象模

    2024年02月05日
    浏览(60)
  • C++ 类和对象(中)构造函数 和 析构函数

     上篇链接:C++ 类和对象(上)_chihiro1122的博客-CSDN博客  我们在C当中,在写一些函数的时候,比如在栈的例子: 如上述例子,用C++ 返回这个栈是否为空,直接返回的话,这栈空间没有被释放,就会内存泄漏,如果我们直接释放这个栈,那么我们就拿不到这个栈是否为空的

    2024年02月02日
    浏览(49)
  • 【C++入门到精通】C++入门 —— 类和对象(构造函数、析构函数)

    目录 一、类的6个默认成员函数  二、构造函数 ⭕构造函数概念 ⭕构造函数的特点 ⭕常见构造函数的几种类型 三、析构函数      ⭕析构函数概念     ⭕析构函数的特点 ⭕常见析构函数的几种类型 四、温馨提示          这一篇文章是上一篇的续集(这里有上篇链接)

    2024年02月15日
    浏览(53)
  • 深入分析C++对象模型之移动构造函数

    接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文和全部的文章列表。 C++11新标准中最重要的特性之一就是引入了支持对象移动的能力,为了支持移动的操作,新标准引入了一种新的引用类

    2024年04月22日
    浏览(49)
  • 【c++】类和对象(三)构造函数和析构函数

    🔥 个人主页 :Quitecoder 🔥 专栏 :c++笔记仓 朋友们大家好,本篇文章我们带来类和对象重要的部分, 构造函数和析构函数 如果一个类中什么成员都没有,简称为空类 任何类在什么都不写时,编译器会自动生成以下6个默认成员函数 (用户没有显式实现,编译器会生成的成

    2024年04月12日
    浏览(49)
  • 【C++】类和对象(中)---构造函数和析构函数

    个人主页:平行线也会相交💪 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 平行线也会相交 原创 收录于专栏【C++之路】💌 本专栏旨在记录C++的学习路线,望对大家有所帮助🙇‍ 希望我们一起努力、成长,共同进步。🍓 我们知道类包含成员变量和成员函数,当一个类中

    2024年02月05日
    浏览(104)
  • C++之构造函数、析构函数、拷贝构造函数终极指南:玩转对象的诞生、生命周期与复制

    W...Y的主页 代码片段分享  前言: 在上篇内容里,我们初识了C++中的类与对象,了解了类的定义、类的实例化、 类的作用域等等,今天我们将继续深入了解类与对象的相关内容,学习构造函数、析构函数与拷贝构造函数,话不多说我们发车!!! 目录 类的6个默认成员函数

    2024年02月06日
    浏览(48)
  • 【C++】类和对象(中)之构造函数与析构函数

    👀 樊梓慕: 个人主页  🎥 个人专栏: 《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》 🌝 每一个不曾起舞的日子,都是对生命的辜负 目录 前言 1.构造函数 1.1概念 1.2特性 2.析构函数 2.1概念 2.2特性 我们继续学习类和对象,今天主要讲解构造

    2024年02月07日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包