C++对象调用优化

2023-11-11

C++对象调用优化

临时对象拷贝构造新对象,临时对象就不会产生!!!

常见的对象调用过程

c++编译器对于对象构造的优化:用临时对象拷贝新对象的时候,临时对象就不产生了,直接构造新对象就可以了

#include<iostream>
using namespace std;

class Test
{
public:
    Test(int data = 10) : ma(data), mb(data) { cout << "Test(int)" << endl; }
    ~Test() { cout << "~Test()" << endl; }
    Test(const Test &t) { cout << "Test(&)" << endl; }
    Test &operator=(const Test &t) { cout << "operator=" << endl; }

private:
    int ma;
    int mb;
};
int main()
{
    Test t1;//普通构造函数
    Test t2(t1);//拷贝构造函数
    Test t3 = t1; // 拷贝构造函数

    t2 = t3;//拷贝构造函数

    // 这里和Test t4(20)没有区别,并不会产生Test(20)临时对象
    Test t4 = Test(20);//普通构造函数
    cout << "-------------------" << endl;

    //显示调用构造函数生成临时对象
    t4 = Test(30); // 普通构造函数,结束这条语句就会调用析构函数
    t4 = (Test)30; // 普通构造函数,结束这条语句就会调用析构函数

    //隐式调用构造函数生成临时对象
    t4 = 30; // 普通构造函数,结束这条语句就会调用析构函数
    cout << "-----------------" << endl;

    return 0;
}


const Test &t1 = Test(20, 20);//仅仅调用了普通构造函数,不会产生临时对象(所以不会有析构)。

对象创建顺序

需要注意的是:static Test t4 = Test(30, 30);这里编译器会优化,临时对象生成新对象,不会产生临时对象,等价于static Test t4(30,30)。Test *p3 = &Test(80, 80); ,这里语句结束后就被析构,p3就是空指针。const Test &p4 = Test(90, 90); ,引用变量就会保存临时对象,所以不会被析构。静态对象的析构在作用域结束之后才进行析构。

#include<iostream>
using namespace std;

class Test
{
public:
    Test(int x = 10, int y = 10) : ma(x), mb(y) { cout << "Test(int,int)" << endl; }
    ~Test() { cout << "~Test()" << endl; }
    Test(const Test &t) { cout << "Test(&)" << endl; }
    Test &operator=(const Test &t) { cout << "operator=" << endl; }

private:
    int ma;
    int mb;
};
Test t1(10, 10);//1.Test(int,int)
int main()
{

    Test t2(20, 20);//3.Test(int,int)
    Test t3 = t2;   // 4.Test(&)
    // 这里编译器会优化,临时对象生成新对象,不会产生临时对象,等价于static Test t4(30,30)
    static Test t4 = Test(30, 30); // 5.Test(int,int),
    t2 = Test(40, 40);             // 6.Test(int,int),operator=,~Test()
    t2 = (Test)(50, 50);           // 7.Test(int,int),operator=,~Test()
    t2 = 60;                       // 8.Test(int,int),operator=,~Test()
    Test *p1 = new Test(70, 70);   // 9.Test(int,int)
    Test *p2 = new Test[2];        // 10.Test(int,int),Test(int,int)
    // Test *p3 = &Test(80, 80);      //可能会报错// 11.Test(int,int),~Test(),这里语句结束后就被析构,p3就是空指针
    const Test &p4 = Test(90, 90); // 12.Test(int,int),引用变量就会保存临时对象,所以不会被析构
    delete p1;                     // 13.~Test()
    delete[] p2;                   // 14.~Test()
    return 0;
}
Test t5(100, 100);//2.Test(int,int)
/*
剩下的析构顺序,p4->t3->t2->t4->t5->t1
t4在t3和t2后面析构的原因是它是静态变量,存储在数据段中,所以在后面析构
*/

函数调用存在的问题

函数调用过程中存在很多对象优化的问题,后面会具体介绍怎么去优化。

#include<iostream>
using namespace std;

class Test
{
public:
    Test(int data = 10) : ma(data) { cout << "Test(int)" << endl; }
    Test(const Test& t) { cout << "Test(&)" << endl; }
    Test& operator=(const Test& t) { cout << "operator=" << endl; return *this; }
    ~Test() { cout << "~Test()" << endl; }
    int getData() { return ma; }

private:
    int ma;
};
Test getObj(Test t)
{
    int val = t.getData();
    Test temp(val);
    return temp;
}
int main()
{
    Test t1;
    Test t2;

    cout << "------------------" << endl;
    t2 = getObj(t1);
    cout << "------------------" << endl;
    Test t3 = getObj(t2);
    cout << "------------------" << endl;

    return 0;
}


函数对象调用过程图。

函数的优化:尽量传递引用,而不是传值;尽量返回临时对象,而不是在函数内部构造出对象。优化后的代码如下所示:

Test getObj(Test& t)
{
    int val = t.getData();
    //Test temp(val);
    return Test(val);
}

可以减少实参到形参的拷贝构造,以及函数内部对象的构造和析构。

函数调用优化总结!!!

函数传递过程中,对象优先按照引用传递,不要按值传递

函数返回对象时,尽量返回一个临时对象,而不是返回一个已经定义好的对象

接收返回值是对象的时候,尽量以初始化的形式接收,而不是以赋值的形式接收

具体可以函数调用存在的问题部分讲解和分析!!!

String的右值引用拷贝构造和操作符重载

#include<iostream>
#include<string.h>

using namespace std;

class String
{
public:
    String(const char* p = nullptr)
    {
        if (p != nullptr)
        {
            _pstr = new char[strlen(p) + 1];
            strcpy(_pstr, p);
        }
        else
        {
            _pstr = new char[1];
            *_pstr = '\0';
        }
        cout << "String(const char*)" << endl;
    }
    ~String()
    {
        cout << "~String()" << endl;
        delete[] _pstr;
        _pstr = nullptr;
    }
    String(const String& src)
    {
        cout << "String(const String&)" << endl;
        _pstr = new char[strlen(src._pstr) + 1];
        strcpy(_pstr, src._pstr);
    }
    String(String&& src)
    {
        //因为是临时对象,所以就没有用const修饰
        cout << "String(const String&&)" << endl;
        _pstr = src._pstr;
        src._pstr = nullptr;
    }
    String& operator=(const String& src)
    {
        cout << "String& operator=(const String&)" << endl;
        if (this == &src)
            return *this;
        delete[] _pstr;
        _pstr = new char[strlen(src._pstr) + 1];
        strcpy(_pstr, src._pstr);
        return *this;
    }
    String& operator=(String&& src)
    {
        //右值引用的操作符重载函数
        cout << "String& operator=(String&&)" << endl;
        if (this == &src)
            return *this;
        delete[] _pstr;
        _pstr = src._pstr;
        src._pstr = nullptr;
        return *this;
    }
    bool operator>(const String& src) const
    {
        return strcmp(_pstr, src._pstr) > 0;
    }
    bool operator==(const String& src) const
    {
        return strcmp(_pstr, src._pstr) == 0;
    }
    bool operator<(const String& src) const
    {
        return strcmp(_pstr, src._pstr) < 0;
    }
    int length()const
    {
        return strlen(_pstr);
    }
    char& operator[](int index) { return _pstr[index]; }
    const char& operator[](int index) const { return _pstr[index]; }
    const char* c_str() const { return _pstr; }

private:
    char* _pstr;
    friend String operator+(const String& lsrc, const String& rsrc);
    friend ostream& operator<<(ostream& out, const String& src);
    friend istream& operator>>(istream& in, String& src);
};

String operator+(const String& lsrc, const String& rsrc)
{
    char* temp = new char[strlen(lsrc._pstr) + strlen(rsrc._pstr) + 1];
    strcpy(temp, lsrc._pstr);
    strcat(temp, rsrc._pstr);
    String s(temp);
    delete[] temp;
    return s;
}
ostream& operator<<(ostream& out, const String& src)
{
    out << src._pstr;
    return out;
}
istream& operator>>(istream& in, String& src)
{
    in >> src._pstr;
    return in;
}

int main()
{
    String str1;
    String str2 = "aaa";
    String str3 = "bbb";
    String str4 = str2 + str3;
    String str5 = str2 + "ccc";
    String str6 = "ddd" + str2;
    
    cout << "----------------------------------" << endl;
    String str7 = String("hello,world");//C++会优化,直接调用构造函数,不会产生临时对象
    str6 = String("hello") + String(",world");//先调用两个构造函数,在调用operator+重载,
    cout << "----------------------------------" << endl;

    return 0;
}

没有右值操作符和拷贝构造函数的情况:


设置了带右值引用的拷贝构造和操作符重载函数。


可以看到原来的使用临时对象生成对象中的函数全部替换为带右值引用的拷贝构造函数,赋值操作符重载也换为带右值引用的拷贝构造函数。使用右值引用的好处主要是节省拷贝构造和操作符重载过程中资源的开辟和释放

String在vector上的使用

move && forward

因为右值引用变量本身是一个左值,所以在一个函数传递右值引用变量的过程中会出现问题,可以如下所示:

void construct(T* p, const T& val)//对象创建
{
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    //这里虽然val是一个右值引用变量,但是还是会调用左值的拷贝构造函数
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, val);
    _last++;
}

move:移动语义,强制获得右值类型。可以实现将一个左值转化为一个右值(这个值其实原来可能就是右值引用变量)。
forward:类型的完美转化,能够识别左值和右值类型。如果原来是左值,那么还是左值;如果原来是右值,那么就传递为右值。

String& &&val->String&一个引用加两个引用还是一个引用。
String&& &&val->String&两个引用加两个引用还是两个引用。
String&& &val->String&两个引用加一个引用还是一个引用,因为右值引用其实本身就是就是一个左值。

后面的val其实就是函数传递过程中的形参类型。

所以上面代码可以考虑更改为以下两种类型:

方式一:move强制转化

void construct(T* p, const T& val)//对象创建
{
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(val);//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void construct(T* p, T&& val)//对象创建
{
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(std::move(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(const T& val)
{
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, val);
    _last++;
}
void push_back(T&& val)
{
    if (full())
        expand();
    _allocator.construct(_last, std::move(val));
    _last++;
}

方式二:forward完美转化

//不管val传递的是左值还是右值,T&& val都能接收
//如果是左值,则接收后还是左值;反之右值,则还是右值
void construct(T* p, T&& val)//对象创建
{
    // 更多new初始化方法 https://blog.csdn.net/qq_45041871/article/details/132251733
    new (p) T(std::forward(val));//使用的是定位new,将val传到地址为p的空间去,p地址指向val
}
void push_back(T&& val)
{
    if (full())
        expand();
    //虽然传入的val是一个右值引用变量,但是它本身是一个左值,
    //所以还是会调用void construct(T* p, const T& val)而不是带右值引用的函数
    _allocator.construct(_last, std::forward(val));
    _last++;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++对象调用优化 的相关文章

  • 如何保证对象只有一个线程

    我有以下代码 class Service public void start creates thread which creates window and goes to message loop void stop sends WM C
  • 类特定的新删除运算符是否必须声明为静态

    标准中是否要求类特定的 new new delete 和 delete 是静态的 我可以让它们成为非静态成员运算符吗 为什么需要它们是静态的 它们被隐式声明为静态 即使您没有键入 static
  • 动态生成的控件 ID 返回为 NULL

    我可以在 Page PreInit 函数中创建动态控件 如何检索控件及其 ID 我的 C 代码用于创建动态控件之一 var btn new WebForms Button btn Text btn ID Addmore btn Click
  • 为什么 set_symmetry_difference 无法与比较器一起使用?

    Example program include
  • C# 构建一个 webservice 方法,它接受 POST 方法,如 HttpWebRequest 方法

    我需要一个接受 POST 方法的 Web 服务 访问我的服务器正在使用 POST 方法 它向我发送了一个 xml 我应该用一些 xml 进行响应 另一方面 当我访问他时 我已经使用 HttpWebRequest 类进行了管理 并且工作正常
  • C++ php 和静态库

    我创建了一个library a 其中包含 cpp 和 h 文件 其中包含很多类 嵌套类和方法 我想在 php 示例中包含这个静态库并尝试使用它 我想提一下 我是 php 新手 我已经在 test cpp 文件中测试了我的 libray a
  • 将二进制数据从 C# 上传到 PHP

    我想将文件从 Windows C 应用程序上传到运行 PHP 的 Web 服务器 我知道 WebClient UploadFile 方法 但我希望能够分块上传文件 以便我可以监控进度并能够暂停 恢复 因此 我正在读取文件的一部分并使用 We
  • 给出 5 个参数,但在终端中只得到 3 个参数

    我想将一个文件传递给一个c 程序 如果我在 IDE 中执行此操作 test string string lt test txt return argc 5 但在终端上我刚刚得到argc 3 看来 这是因为 什么是 lt 意思是 我正在使用
  • AES 输出是否小于输入?

    我想加密一个字符串并将其嵌入到 URL 中 因此我想确保加密的输出不大于输入 AES 是可行的方法吗 不可能创建任何始终会创建比输入更小的输出的算法 但可以将任何输出反转回输入 如果您允许 不大于输入 那么基本上您只是在谈论同构算法alwa
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • 每个租户的唯一用户名和电子邮件

    我正在使用以下代码编写多租户应用程序ASP NET Core 2 1 我想覆盖默认的与用户创建相关的验证机制 目前我无法创建多个具有相同的用户UserName My ApplicationUser模型有一个名为TenantID 我想要实现的
  • ASP.NET MailMessage.BodyEncoding 和 MailMessage.SubjectEncoding 默认值

    很简单的问题 但我在 MSDN 上找不到答案 查找 ASP NET 将用于的默认值 MailMessage BodyEncoding and MailMessage SubjectEncoding 如果你不在代码中设置它们 Thanks F
  • 我可以让 ungetc 取消阻止阻塞的 fgetc 调用吗?

    我想在收到 SIGUSR1 后使用 ungetc 将 A 字符重新填充到标准输入中 想象一下我有充分的理由这样做 调用 foo 时 stdin 中的阻塞读取不会被收到信号时的 ungetc 调用中断 虽然我没想到它会按原样工作 但我想知道是
  • 使用restsharp序列化对象并将其传递给WebApi而不是序列化列表

    我有一个看起来像的视图模型 public class StoreItemViewModel public Guid ItemId get set public List
  • 新任务中使用的依赖注入服务

    我在需要时使用依赖项注入来访问我的服务 但我现在想要创建一个并发任务 但这会由于依赖项注入对象及其生命周期而导致问题 我读过这篇文章 标题 防止多线程 Link http mehdi me ambient dbcontext in ef6
  • 了解使用 Windows 本机 WPF 客户端进行 ADFS 登录

    我已经阅读了大量有关 ADFS 与 NodeJS Angular 或其他前端 Web 框架集成以及一般流程如何工作的文献 并通过 Auth0 Angular 起始代码构建了概念证明 但我不明白如何这可以与本机 WPF Windows 应用程
  • 跨多个域的 ASP.NET 会话

    是否有合适的 NET 解决方案来在多个域上提供持久服务器会话 即 如果该网站的用户在 www site1 com 下登录 他们也将在 www site2 com 下登录 安全是我们正在开发的程序的一个问题 Thanks 它是否需要在会话中
  • 将 char[][] 转换为 char** 会导致段错误吗?

    好吧 我的 C 有点生疏了 但我想我应该用 C 来做我的下一个 小 项目 这样我就可以对其进行抛光 并且我已经有不到 20 行的段错误了 这是我的完整代码 define ROWS 4 define COLS 4 char main map
  • 使我的 COM 程序集调用异步

    我刚刚 赢得 了在当前工作中维护用 C 编码的遗留库的特权 这个dll 公开使用 Uniface 构建的大型遗留系统的方法 除了调用 COM 对象之外别无选择 充当此遗留系统与另一个系统的 API 之间的链接 在某些情况下 使用 WinFo
  • ASP.NET Core MVC 视图组件搜索路径

    在此处的文档中 https learn microsoft com en us aspnet core mvc views view components view aspnetcore 2 2 https learn microsoft

随机推荐

  • [Hive]一篇带你读懂Hive是什么

    作者简介 大家好 我是Philosophy7 让我们一起共同进步吧 个人主页 Philosophy7的csdn博客 系列专栏 哲学语录 承认自己的无知 乃是开启智慧的大门 如果觉得博主的文章还不错的话 请点赞 收藏 留言 支持一下博 gt
  • 组合优化问题求解算法思路的整理(VRP、SDVRP,container loading)

    一 技术背景与合作的必要性 解决合作问题现有的技术路线 挑战与不足 拟采用的技术路线 合作引进 这种技术的有益效果 缺 求解组合优化问题可以通过利用各种数学方法 寻找离散事件的最优编排 分组 次 序或筛选等 目前常用的优化算法可以分为以下四
  • 【LaTeX 教程】04. LaTeX 插入数学公式与符号

    LaTeX 教程 04 LaTeX 插入符号与数学公式 LaTeX 公式 我将把握最近文章里用到的数学公式格式都放上来供大家参考学习 首先 最简单的数学模式 xxx 一个 符号 中间的内容是行内模式 xxx 两个 符号 中间的内容是行间模式
  • 使用wxml2canvas将微信小程序页面转为图片

    最近有个微信小程序项目 需要将页面转为图片 微信小程序提供的Api是wx canvasToTempFilePath 这个方法是将画布指定区域的内容导出生成指定大小的图片 但是我们是将页面导出图片 所以可以使用wxml2canvas解决 1
  • WINRAR常用命令

    这段时间因为工作的需要 研究了一下关于WINRAR的操作 一下是关于它的一些常用命令 一 压缩命令1 将temp txt压缩为temp rarrar a temp rar temp txt 2 将当前目录下所有文件压缩到temp rarra
  • 编程器烧写NAND flash的一些说明

    注意事项 1 大小端模式 也即在使用编程器时需不需要做字节反序 2 Spare area处理方式 需要还是不需要 是否含有私有ECC算法 3 坏块处理方式 摘要一段说明如下 虽然针对西尔特SUPERPRO 9000U的文章 但也对许多其他的
  • ERROR: Failed to parse POMs org.apache.maven.project.ProjectBuildingException: Some problems were en

    问题 ERROR Failed to parse POMs org apache maven project ProjectBuildingException Some problems were encountered while pro
  • 数据分析毕业设计 全国疫情数据分析与3D可视化 - python 大数据

    文章目录 0 前言 1 课题背景 2 实现效果 3 设计原理 4 部分代码 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求 这两年不断有学弟学妹告诉学长自己做的项目系统达不
  • Java入门之二维向量定义及相加

    6 2 二维向量定义及相加 Java 10 分 裁判测试程序样例中展示的是一段二维向量类TDVector的定义以及二维向量求和的Java代码 其中缺失了部分代码 请补充完整 以保证测试程序正常运行 函数接口定义 提示 需要补充的成员方法有
  • 最优解算法的讨论

    不懂优化的人希望能有通用的方法来解决他手头的问题 但不幸的事没有这种方法存在 快速的方法都需要某些条件 比如常见的有强凸 线性 可分解啥的 目前研究的比较成熟的就是强凸光源可分解 非凸没有特别有效的方法来解 如果是强凸的 何必用那么复杂的方
  • python实战-读取xlsx表格批量替换文件名

    文章目录 一 前言 二 解决思路 三 具体代码实现 四 总结 一 前言 一位在校当老师的同学遇到了一个需求 学生1寸照片是以学生姓名命名 现在需要重命名1寸照片 重命名为exel里对应的学生姓名的身份证号码 我心想这个需求很容易实现 照片有
  • springboot初始化修改yml文件并加载

    package com lezu springboot config import org springframework core io ClassPathResource import org springframework core
  • Pytorch dataloader中的num_workers (选择最合适的num_workers值)

    num workers是Dataloader的概念 默认值是0 是告诉DataLoader实例要使用多少个子进程进行数据加载 和CPU有关 和GPU无关 如果num worker设为0 意味着每一轮迭代时 dataloader不再有自主加载
  • 使用fo-dicom读取Dicom文件的PixelData信息及像素信息(C# / fo-dicom)

    安装fo dicom 在vs中安装fo dicom 点击解决方案 右键选择管理解决方案的NuGet程序包 打开窗口后 在浏览框输入fo dicom进行搜索 然后选择fo dicom 勾选后点击安装即可 读取Dicom文件并获取PixelDa
  • 智能指针与句柄详解(一)

    前言 智能指针与引用计数详解 一 中提到实现智能指针有两种方法 一种是引用计数 另一种就是句柄类实现 什么是句柄类 句柄类是用来存储和管理基类指针 指针所指对象的类型可以变化 它既可以指向基类类型对象又可以指向派生类型对象 用户通过句柄类访
  • C++ 版 Opencv4 通过迭代器访问Mat类矩阵中的元素错误

    错误说明 很多教程中的示例代码如下 cv Mat a 3 4 CV 8UC3 cv Scalar 1 2 3 cv MatIterator
  • 手机端分页上拉下拉动画样式

    pullload scss pull load position relative overflow y scroll webkit overflow scrolling touch pull load head position abso
  • 微信小程序 webview mp4视频 在ios无法播放 安卓和谷歌浏览器正常

    记一次微信小程序开发 视频兼容问题 参考链接 https www jianshu com p 31f0593496ef 问题描述 微信小程序 内嵌webview 利用video标签播放流媒体视频 部分视频 报错 error 问题确认 安卓正
  • 2023最新网络安全毕业设计题目选题大全

    0 简介 毕业季马上就要开始了 不少同学询问学长网安专业选题以及开题相关的问题 今天跟大家分享信息安全毕设选题 最新的信息安全 网络安全 专业毕设选题 难度适中 适合作为毕业设计 大家参考 学长整理的题目标准 相对容易 工作量达标 题目新颖
  • C++对象调用优化

    C 对象调用优化 临时对象拷贝构造新对象 临时对象就不会产生 常见的对象调用过程 c 编译器对于对象构造的优化 用临时对象拷贝新对象的时候 临时对象就不产生了 直接构造新对象就可以了 include