前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++17常用新特性(五)---强制省略拷贝或传递未实质化的对象

C++17常用新特性(五)---强制省略拷贝或传递未实质化的对象

作者头像
CPP开发前沿
发布2022-04-13 15:29:31
1.2K0
发布2022-04-13 15:29:31
举报
文章被收录于专栏:CPP开发前沿

从上帝视角看。本文主要包含两个方面:

  • 1、从技术上的角度说,通过技术演进减少值拷贝或者临时对象传递时对象的拷贝构造;
  • 2、从效果上说实际上是传递了一个没有进行实质化的对象。ps:unmaterialized object这个具体怎么翻译大家仁者见仁。

1 省略临时拷贝缘起

从C++标准产生一直到C++17,C++标准一直在试图减少某些临时变量或者拷贝的操作,虽然经过优化后,可能在实际执行中不需要调用拷贝或者移动构造,但是它必须隐士或者显示存在,如下面的案例,如果在类中禁止编译器默认生成拷贝构造和移动构造函数,代码将不会被编译通过。

代码语言:javascript
复制
//代码在C++14版本中编译
class MyClass
{
public:
    MyClass(){cout<<"MyClass() initialize..."<<endl;}
    MyClass(const MyClass &myClass)=delete;
    MyClass(MyClass &&myClass)=delete;
};

MyClass bar() {
    return MyClass{};
}
void foo(MyClass param) { // param用 传 递 进 入 的 实 参 初 始 化
   cout<<"foo function..."<<endl;
}
int main()
{
    foo(MyClass{});
    MyClass barClass = bar();
    foo(bar());
    return 0;

编译后,编译器会报如下错误,该错误产生的原因就是因为在类中限制了拷贝构造和移动构造的默认生成。

从C++17起,上面的代码就可以编译通过了,因为C++17直接强制在临时对象中强制省略了对象的拷贝。但是,C++17还不都彻底,当代码中包含一个具名的变量并作为返回值时依然会调用拷贝构造函数。如下面的代码段:

代码语言:javascript
复制
MyClass bar() {
    MyClass myClassObj;
    retutn myClassObj;
}

上面的代码在执行时依旧会需要拷贝构造或者移动构造,因为临时对象作为返回值时会触发编译器具名返回值优化。除此之外,以下场景也会调用拷贝或者移动拷贝构造。

代码语言:javascript
复制
void foo(MyClass obj) { 
   cout<<"foo function..."<<endl;
   return obj;
}

上面的代码中,调用拷贝构造或者移动构造是有条件的,如果传进去的形参没有作为函数值返回是不会调用,作为返回值时才会需要,因为返回的对象是具名的。

2 强制省略临时拷贝的优势

强制省略临时拷贝的优势主要有两点:

  • 可以提升性能,强制省略临时拷贝比进行部分的不拷贝性能依旧可以带来很大的提升。很多主流的编译器在编译时已经对代码进行了优化,但是这种优化一般根据具体的编译器而定,C++17后这种优化变成了一种标准。
  • 可以返回不允许拷贝或者移动的对象。如下面的泛型函数:
代码语言:javascript
复制
template<typename T,typename ...Args>
T factory(Args&& ...args){
    return T{std::forward<Args>(args)...};
}
int main()
{
    int iValue = factory<int>(100);
    cout<<"iValue="<<iValue<<endl;
    std::unique_ptr<int> pValue =factory<std::unique_ptr<int>>(new int{101});
    cout<<"*pValue="<<*pValue<<endl;
    std::atomic<int> aValue = factory<std::atomic<int>>(42);
    cout<<"aValue="<<aValue<<endl;
    return 0;
}

在上面的代码中,泛型函数除了可以用于一般变量的创建外还可以对atomic类型进行创建。在泛型函数中使用了完美转发,具体可以参考下文:

【C++11】 改成程序性能的方法--完美转发

除此之外,在C++17之后类中禁止移动构造函数的默认生成在实际使用时可以正常编译和运行,但是在C++17之前是编译不过的,因为在实际使用时都会调用到移动构造函数。代码如下:

代码语言:javascript
复制
class MyClass
{
public:
    MyClass(){cout<<"MyClass() initialize..."<<endl;}
    MyClass(int value){cout<<"MyClass(int) initialize..."<<value<<endl;}
    MyClass(const MyClass &)=default;
    MyClass(MyClass &&)=delete;
};

MyClass bar() {
    return MyClass{};
}

int main()
{
    MyClass barClass = bar();
    MyClass iValue = 4;
    return 0;
}

C++17之后,上面的代码就可以正常编译运行,运行结果为:

3 值类型体系 (value category)的变更

东西虽好,但是会伤筋动骨,虽然C++17 明确强制省略了临时拷贝,但是也需要做一系列的配套改动。为了配合改造,C++值类型体系进行了很多改造。

C++值类型体系可以分为三个阶段,分别是:C++11之前的值类型体系,C++11后到C++17期间的值类型体系以及C++17后的值类型体系。具体如下:

3.1 C++11之前的值类型体系

C++11之前。值类型体系主要是从C语言继承而来。划分也比较简单,主要根据赋值语句进行划分,分为左值和右值。如下面的语句:

代码语言:javascript
复制
string strValue="Hello World";

上面语句中,strValue是左值,"Hello World"是右值;当ANSIC出现后,如果在左值前面加上了const限定符。strValue将不能进行赋值,但它依然是一个左值。C++11后因为移动对象又引入了类型到期值,而原来的右值又被称之为纯右值。

3.2 C++11起的值类型体系

自从 C++11 起,值类型有了核心的值类型体系 lvalue(左值), prvalue(纯右值)(”purervalue”) 和 xvalue(到期值)(”eXpiring value”)。而复合的值类型体系有 glvalue(广义左值)(”generalized lvalue”,它是 lvalue 和 xvalue 的复合)和 rvalue(右值)(xvalue 和 prvalue 的复合)

C++11的值类型结构如下:

3.3 C++17起的值类型体系

从C++17起,值类型体系被明确了定义,重新明确后的值类型如下图所示:

从广义来说,值类型主要包含两种形式,分如下:

  • glvaue: 主要是描述对象或函数位置的表达式。
  • prvalue:主要是用来表示进行初始化的表达式。而xvalue是他们之间存在的交叉,表示的是一个资源可以被回收利用的对象。

C++17 引入了实质化 (materialization),这一新的属于主要是针对临时对象。prvalue 就是一种临时对象。因此,临时对象实质化转换实际上就是一种 prvalue 到 xvalue 的转换。

在实际编程时,prvalue 出现在需要 glvalue(lvalue 或者 xvalue)的地方都是有效的,它通过创建一个临时对象prvalue,并用该临时对象完成值的初始化。如下面的代码示例:

代码语言:javascript
复制
void f(const X& p);
f(X());

在上面的代码中,f的参数是一个引用,因此需要一个glvalue的对象。但是x()返回的是一个prvalue,这时。临时变量实质化规则就会呗唤起,将prvalue既X()转换为一个xvalue对象。值得注意的是,这个过程中并没有产生新的对象。

prvalue已经不再是一个对象,而是一个可以进行初始化对象的表达式,因此使用prvalue初始化对象时不需要进行拷贝而是可以进行移动的。这样确保了省略临时对象的拷贝操作可以完美实现。

4 未实质化的返回值传递

以值返回临时对象 (prvalue) 的过程都是在传递未实质化的返回值,主要有以下场景:

  • 函数返回一个常量值
代码语言:javascript
复制
int func()
{
  return 38;
}
  • 以auto或者类型名作为函数返回类型时
代码语言:javascript
复制
MyClass bar()
{
    return MyClass{};
}
  • 使用类型推导auto类型时,如decltype(auto)
代码语言:javascript
复制
decltype(auto) bar()
{
    return MyClass{};
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CPP开发前沿 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档