C++/CLI编程-属性property的封装实现

news2024/10/7 1:59:48

1.前言

        上一篇博文中提到过C++/CLI典型使用场景之一是为native C++编写的算法封装接口,进而在C#项目中调用。一种典型的应用是作为Wrapper封装层来封装C++库的类和接口、做C++/C#类型的转换,然后在C#应用程序中调用,开发便捷易用的应用程序。

C++/CLI编程知识点小记_c++cli-CSDN博客文章浏览阅读1.5k次,点赞15次,收藏17次。本篇博文并非详细的C++/CLI教程,仅是博主就学习和实践总结的部分知识点记录。第一次接触C++/CLI是2017年了,用C++编写底层库,C++/CLI编写wrapper层,在C#项目中进行调用,开发应用。_c++clihttps://blog.csdn.net/baidu_38621657/article/details/142326652        C++类中经常包含字段定义,当字段为基本类型时,CLI封装类以属性(property)方式为C++类字段成员封装相对比较简单,但当字段为STL容器类型时,字段封装就比较复杂一点,其实也不算复杂,只是需要考虑一些关键点。

2.内容

CLI封装的ref class本质上是.NET框架中实现的,尽管可以使用C++/C#混合编程,可以使用native C++和managed C++,这就需要考虑一个关键点:

关键点1:

property属性get返回的字段一般可以被修改其本身的值(如其成员字段);

ref class本质上是.NET框架下的类,其遵循.NET对象管理和回收机制,包括引用类型传递和管理;

2.1基本类型

一般来说基本类型(int、float、double、array等)可以用property封装,以方便的控制和处理对应字段的读写,可参考:How to: Use Properties in C++/CLI | Microsoft LearnLearn more about: How to: Use Properties in C++/CLIicon-default.png?t=O83Ahttps://learn.microsoft.com/en-us/cpp/dotnet/how-to-use-properties-in-cpp-cli?view=msvc-170

2.2 非基本类型 

如果字段是非基本类型(如STL或自定义类型等),使用property封装时就要慎重了,这时要分情况处理。

2.2.1 CLI类(ref class)AWrapper是对C++类A的封装

        如果CLI类(ref class)AWrapper是对C++类A的封装,其包含一个类型为A*的成员字段,封装了A类型的字段和成员函数,这时候建议不要使用property对A类型字段进行封装,因为很难满足上述关键点1的要求,如下述代码:

public ref class LineWrapper
    {
    public:
        LineWrapper()
        {
            mImpl = new Line();
        }
        LineWrapper(const LineWrapper% other)
        {
            mImpl = new Line(*other.mImpl);
        }
        LineWrapper(const Line* impl)
        {
            mImpl = new Line(*impl);
        }
        LineWrapper(Vector3Wrapperf^ pt0, Vector3Wrapperf^ pt1)
        {
            mImpl = new Line();
            mImpl->pt0 = *pt0->GetImplPtr();
            mImpl->pt1 = *pt1->GetImplPtr();
        }
        ~LineWrapper()
        {
            delete mImpl;
        }

        property Vector3Wrapperf^ Pt0
        {
            Vector3Wrapperf^ get()
            {
                Vector3f pt = mImpl->pt0;
                return gcnew Vector3Wrapperf(pt.X, pt.Y, pt.Z);
            };

            void set(Vector3Wrapperf^ pt)
            {
                mImpl->pt0.X = pt->X;
                mImpl->pt0.Y = pt->Y;
                mImpl->pt0.Z = pt->Z;
            };
        }

        property Vector3Wrapperf^ Pt1
        {
            Vector3Wrapperf^ get()
            {
                Vector3f pt = mImpl->pt1;
                return gcnew Vector3Wrapperf(pt.X, pt.Y, pt.Z);
            };

            void set(Vector3Wrapperf^ pt)
            {
                mImpl->pt1.X = pt->X;
                mImpl->pt1.Y = pt->Y;
                mImpl->pt1.Z = pt->Z;
            };
        }

        void SetImplPtr(Line* ptr)
        {
            delete mImpl;
            mImpl = ptr;
        }

        Line* GetImplPtr()
        {
            return mImpl;
        }

    private:
        Line* mImpl;
    };

当我们在C#项目中写下如下代码时回发生什么行为?

var line = new LineWrapper();
line.Pt0 = new Vector3Wrapperf(100.0f,0.0f,0.0f);  //和预期行为一致
line.Pt0.X = 200.0f;  //和预期行为不一致!并没有成功赋值。原因:调用property Pt0 get返回gcnew 新对象,对新对象的X进行了赋值,并没有对line.Pt0的X进行赋值

第3行和预期行为不一致!并没有成功赋值。原因:调用property Pt0 get返回gcnew 新对象,对新对象的X进行了赋值,并没有对line.Pt0的X进行赋值。

如果我们尝试在ref class LineWrapper中添加成员字段Vector3Wrapperf^ mPt0,并且让类型为Vector3f的(ref class LineWrapper也是对C++类Vector3f的封装,实现手法和上述类似)mPt0->mImpl指向mImpl->pt0,这个问题会解决吗?

public ref class LineWrapper
    {
    public:
        LineWrapper()
        {
            mImpl = new Line();
            InitPt0();
            InitPt1();
        }
        LineWrapper(const LineWrapper% other)
        {
            mImpl = new Line(*other.mImpl);
            InitPt0();
            InitPt1();
        }
        LineWrapper(const Line* impl)
        {
            mImpl = new Line(*impl);
            InitPt0();
            InitPt1();
        }
        LineWrapper(Vector3Wrapperf^ pt0, Vector3Wrapperf^ pt1)
        {
            mImpl = new Line();
            mImpl->pt0 = *pt0->GetImplPtr();
            mImpl->pt1 = *pt1->GetImplPtr();
            InitPt0();
            InitPt1();
        }
        ~LineWrapper()
        {
            delete mImpl;
        }

        property Vector3Wrapperf^ Pt0
        {
            Vector3Wrapperf^ get()
            {
                return mPt0;
            };

            void set(Vector3Wrapperf^ pt)
            {
                mImpl->pt0.X = pt->X;
                mImpl->pt0.Y = pt->Y;
                mImpl->pt0.Z = pt->Z;
            };
        }

        property Vector3Wrapperf^ Pt1
        {
            Vector3Wrapperf^ get()
            {
                return mPt1;
            };

            void set(Vector3Wrapperf^ pt)
            {
                mImpl->pt1.X = pt->X;
                mImpl->pt1.Y = pt->Y;
                mImpl->pt1.Z = pt->Z;
            };
        }

        void SetImplPtr(Line* ptr)
        {
            delete mImpl;
            mImpl = ptr;
        }

        Line* GetImplPtr()
        {
            return mImpl;
        }
    private:
        void InitPt0()
        {
            mPt0 = gcnew Vector3Wrapperf();
            mPt0->setImplPtr(&mImpl->pt0);
        }

        void InitPt1()
        {
            mPt1 = gcnew Vector3Wrapperf();
            mPt1->setImplPtr(&mImpl->pt1);
        }

    private:
        Line* mImpl;
        Vector3Wrapperf^ mPt0;  //保证mPt0内的类型为Vector3f的mImpl指针始终指向mImpl->pt0
        Vector3Wrapperf^ mPt1;  //同上
    };

这样处理会解决部分问题,如上述第3行代码按预期行为执行,但如果property类型为List<Vector3Wrapperf^>^时,上述就会有新问题:

var test = new LineWrapper(new Vector3Wrapperf(-100.0f, -100.0f, -100.0f), new Vector3Wrapperf(100.0f, -100.0f, -100.0f));

face.OuterLoop.LstLine[0] = test;  //没有按预期执行!调用property LstLine get,返回gcnew List<LineWrapper>,处理后并没有对face.OuterLoop.LstLine中位序为0的line进行赋值!
face.OuterLoop.LstLine[0].Pt0.Z = 200.0f;  //按预期执行

此外由于mPt0是托管对象,在LineWrapper被释放时,会执行delete mImpl;同时可能其他类型为Vector3Wrapperf的C#对象b可能指向和LineWrapper中的mPt0,这时就可能发生b还在生命周期,但已失去“行动力”的情况,因为其内部赖以生存的mImpl指向的对象已经在delete后释放了,出现了野指针情况!

建议如2.2.1节开始所述,对于非基本类型使用下述函数而不使用property,如下述代码。

public ref class LineWrapper
    {
    public:
        LineWrapper()
        {
            mImpl = new Line();
        }
        LineWrapper(const LineWrapper% other)
        {
            mImpl = new Line(*other.mImpl);
        }
        LineWrapper(const Line* impl)
        {
            mImpl = new Line(*impl);
        }
        LineWrapper(Vector3Wrapperf^ pt0, Vector3Wrapperf^ pt1)
        {
            mImpl = new Line();
            mImpl->pt0 = *pt0->GetImplPtr();
            mImpl->pt1 = *pt1->GetImplPtr();
        }
        ~LineWrapper()
        {
            delete mImpl;
        }

        void setPt0(Vector3Wrapperf^ pt)
        {
            mImpl->pt0.X = pt->X;
            mImpl->pt0.Y = pt->Y;
            mImpl->pt0.Z = pt->Z;
        }

        Vector3Wrapperf^ getPt0()
        {
            return gcnew Vector3Wrapperf(mImpl->pt0.X, mImpl->pt0.Y, mImpl->pt0.Z);
        }

        void setPt1(Vector3Wrapperf^ pt)
        {
            mImpl->pt1.X = pt->X;
            mImpl->pt1.Y = pt->Y;
            mImpl->pt1.Z = pt->Z;
        }

        Vector3Wrapperf^ getPt1()
        {
            return gcnew Vector3Wrapperf(mImpl->pt1.X, mImpl->pt1.Y, mImpl->pt1.Z);
        }

        void SetImplPtr(Line* ptr)
        {
            delete mImpl;
            mImpl = ptr;
        }

        Line* GetImplPtr()
        {
            return mImpl;
        }

    private:
        Line* mImpl;
    };

2.2.2 CLI类(ref class)AWrapper不依赖C++类A实现

CLI类(ref class)AWrapper和C++类A没有直接关联,相关的算法需要进行AWrapper和A之间的转换,这种情况就像写C#类,对于基本类型和非基本类型都可以正常使用property进行封装,这里不再赘述。

3.总结

用C++/CLI对C++算法进行封装时可以有两种方案:

  1. 对需要封装的C++类进行CLI封装,包含类型为对应C++类型指针的成员字段mImpl,对非基本类型不使用property封装,而使用函数封装,见上文;
  2. 如2.2.2节所属,C++/CLI中定义不依赖C++类型的Wrapper类,而在相关的算法封装中实现好C++和CLI类型的转换即可,如C++ class Line和CLI ref class LineWrapper中都有pt0、pt1字段,两class没有直接关系,在C++/CLI算法封装中进行两类变量的转换即可,如:
    1. )将C#传递过来的CLI变量转为C++类型变量;
    2. )然后调用C++算法接口,获取类型为C++类型的结果变量;
    3. )将类型为C++类型的结果变量转为CLI类型的变量返回给C#调用;
    4. )在C#中正常组织参数调接口,获取结果,使用即可;

图形几何、数据处理、并行计算相关研究和研发,公众号:geometrylib,欢迎交流。 

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

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

相关文章

Bellman-Ford算法和SPFA算法

Bellman-Ford算法 能够处理存在负边权的情况。 算法时间复杂度:O(n*m)&#xff0c;n是顶点数&#xff0c;m是边数。 算法实现: 设s为起点&#xff0c;dis[v]即为s到v的最短距离&#xff0c;pre[v]为v前驱。w[j]是边j的长度&#xff0c;且j连接u、v。 dis[s] 0;dis[v] 0x3…

(Linux驱动学习 - 5).Linux 下 DHT11 温湿度传感器驱动编写

DHT11的通信协议是单总线协议&#xff0c;可以用之前学习的pinctl和gpio子系统完成某IO引脚上数据的读与写。 一.在设备树下添加dht11的设备结点 1.流程图 2.设备树代码 &#xff08;1&#xff09;.在设备树的 iomuxc结点下添加 pinctl_dht11 &#xff08;2&#xff09;.在根…

探索大型语言模型在文化常识方面的理解能力与局限性

介绍 论文地址&#xff1a;https://arxiv.org/pdf/2405.04655v1 近年来&#xff0c;大型语言模型&#xff08;LLM&#xff09;不仅被广泛应用于各个领域&#xff0c;而且通过大量的基准评估&#xff0c;证明它们能够理解人类所拥有的常识&#xff08;Commonsense&#xff09;…

FLUX的ID保持项目也来了! 字节开源PuLID-FLUX-v0.9.0,开启一致性风格写真新纪元!

之前的文章已经和大家介绍过字节开源的ID保持项目PuLID。随着FLUX模型的发布&#xff0c;PuLID也开源了 FLUX 版本的模型&#xff0c;不得不说FLUX的强大&#xff0c;两个月生态就赶上了SDXL。这次新发布PuLID-FLUX-v0.9.0模型&#xff0c;它为FLUX.1-dev提供了无需调整的ID定制…

简单部署vue+springboot项目

vue 参考博客 先将vue项目打包 npm run build 再创建项目文件夹front,在front中新建nginx.conf server {listen 80;server_name localhost;# 请求体的大小限制client_max_body_size 50m;# 日志文件存放地址access_log /var/log/nginx/host.access.log main;error…

嵌入式知识点复习(一)

国庆倒数第二天&#xff0c;进行嵌入式课堂测试的复习&#xff1a; 第一章 绪论 1.1 嵌入式系统的概念 嵌入式系统定义 嵌入式系统定位 嵌入式系统形式 嵌入式系统三要素 嵌入式系统与桌面通用系统的区别 1.2 嵌入式系统的发展历程 微处理器的演进历史 单片机的演进历史 …

学习使用Cube软件

一、点亮LED灯 1、新建项目 File → New → STM32 Project搜索芯片信号项目名称 弹窗点击Yes 2、点亮LED 配置GPIO为输出模式 细化配置 保存&#xff08;ctrl S&#xff09;自动生成代码 手动生成代码 选择跳转到代码页面

【d60】【Java】【力扣】509. 斐波那契数

思路 要做的问题&#xff1a;求F&#xff08;n&#xff09;, F&#xff08;n&#xff09;就等于F(n-1)F(n-2)&#xff0c;要把这个F(n-1)F(n-2)当作常量&#xff0c;已经得到的值&#xff0c; 结束条件&#xff1a;如果是第1 第2 个数字的时候&#xff0c;没有n-1和n-2,所以…

系统设计,如何设计一个秒杀功能

需要解决的问题 瞬时流量的承接防止超卖预防黑产避免对正常服务的影响兜底方法 前端设计 利用 CDN 缓存静态资源&#xff0c;减轻服务器的压力在前端随机限流按钮防抖&#xff0c;防止用户重复点击 后端设计 Nginx 做统一接入&#xff0c;进行负载均衡与限流用 sentinel 等…

Kron Reduction消去法如何操作,矩阵推导过程

三阶矩阵消去单节点 在电力系统中,母线上的电流注入始终为0,这样的节点可以通过一定的方法消除。以三节点为例,假设注入节点3的电流为0,则: [ I 1 I 2 I 3 ] = [ I 1 I 2 0 ] = [ Y 11 Y 12 Y 13 Y 21 Y 22 Y 23 Y 31 Y 32 Y 33 ] [ V 1 V 2 V 3 ] \left[\begin{array}{…

交叉熵的数学推导和手撕代码

交叉熵的数学推导和手撕代码 数学推导手撕代码 数学推导 手撕代码 import torch import torch.nn.functional as F# 二元交叉熵损失函数 def binary_cross_entropy(predictions, targets):# predictions应为sigmoid函数的输出&#xff0c;即概率值# targets应为0或1的二进制标…

一个值得关注的3D生成新算法:速度和图像生成平齐,能生成合理的展开贴图和高质量mesh

今天跟大家介绍的GIMDiffusion是一种新的Text-to-3D模型&#xff0c;利用几何图像&#xff08;Geometry Images&#xff09;来高效地表示3D形状&#xff0c;避免了复杂的3D架构。通过结合现有的Text-to-Image模型如Stable Diffusion的2D先验知识&#xff0c;GIMDiffusion能够在…

系统架构设计师论文《论NoSQL数据库技术及其应用》精选试读

论文真题 随着互联网web2.0网站的兴起&#xff0c;传统关系数据库在应对web2.0 网站&#xff0c;特别是超大规模和高并发的web2.0纯动态SNS网站上已经显得力不从心&#xff0c;暴露了很多难以克服的问题&#xff0c;而非关系型的数据库则由于其本身的特点得到了非常迅速的发展…

LeetCode讲解篇之139. 单词拆分

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用一个数组记录字符串s在[0, i)区间能否使用wordDict组成 我们使用左右指针遍历字符串s的子串&#xff0c;左指针 j 为子串的左端点下标&#xff0c;右指针 i 为右端点下标的下一个 遍历过程中如果字符串s…

利士策分享,哀牢山:网红打卡地背后的探险风险

利士策分享&#xff0c;哀牢山&#xff1a;网红打卡地背后的探险风险 最近&#xff0c;云南的哀牢山因其独特的自然风光和神秘探险氛围而迅速走红网络。许多游客&#xff0c;特别是户外探险爱好者&#xff0c;纷纷涌入这片神秘的山脉&#xff0c;想要亲身体验那份原始与野性的…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-06

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-06 目录 文章目录 计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-06目录1. A LLM-Powered Automatic Grading Framework with Human-Level Guidelines Optimization摘要&#xff1a;研究背景&…

MATLAB智能优化算法-学习笔记(4)——灰狼优化算法求解旅行商问题【过程+代码】

灰狼优化算法(Grey Wolf Optimizer, GWO)是一种基于灰狼社会行为的元启发式算法,主要模拟灰狼群体的捕猎行为(包括围攻、追捕、搜寻猎物等过程)。多旅行商问题(Multi-Traveling Salesman Problem, mTSP)是旅行商问题(TSP)的扩展,它涉及多个旅行商(车辆)从一个起点城…

超好用的element的el-pagination分页组件二次封装-附源码及讲解

前言&#xff1a;在很多后台管理系统开发时总会有很多分页组件的使用&#xff0c;如果我们每次都用elementui官网的el-pagination去写的话&#xff0c;调整所有分页的样式就会很麻烦&#xff0c;而且页面内容也会很累赘繁琐。 讲解一个我经常使用的二次封装el-pagination组件&…

产品经理产出的原型设计 - 需求文档应该怎么制作?

需求文档&#xff0c;产品经理最终产出的文档&#xff0c;也是产品设计最终的表述形式。本次分享呢&#xff0c;就是介绍如何写好一份需求文档。 所有元件均可复用&#xff0c;可作为管理端原型设计模板&#xff0c;按照实际项目需求进行功能拓展。有需要的话可分享源文件。 …

免费版U盘数据恢复软件大揭秘,拯救你的重要数据

我们的生活和工作越来越离不开各种存储设备&#xff0c;其中优盘因其小巧便携、方便使用的特点&#xff0c;成为了我们存储和传输数据的重要工具之一。为了防止你像我一样会遇到数据丢失抓狂的情况&#xff0c;我分享几款u盘数据恢复软件免费版工具来即时补救。 1.福昕U盘数据…