Inside C++ object model: Program Transformation Semantics

程序转换语义(Program Transformation Semantics)

        看到以下代码,你会想到哪些?也许你会有以下两个推断:

  1. 每次调用GetNode时,都会以值的方式返回一个Node对象。
  2. 每次调用GetNode时,都会调用Node类的拷贝构造函数。
class Node;

Node GetNode()
{
    Node n;
    return n;
}

        以上两个推断看起来合情合理,但通过上一节我们知道,Node是否具有拷贝构造函数,取决于其自身的定义,而且编译器有可能在背后偷偷优化你的代码,所以以上两个推断是否正确,一方面要看类Node的定义;另一方面也要看编译器有没有做优化。

显式的初始化(Explicit initialization)

        现在有一个对象的定义,Node n。然后有3个基于对象n的显式初始化如下,

void func()
{
    Node n1(n);
    Node n2 = n;
    Node n3 = Node(n);
}

那么这个代码片段所需要的程序转换就有两部分:1. 以非初始化的方式重写每个定义;2. 插入对拷贝构造函数的调用。转换后程序的伪代码如下:

Node n;

void func()
{
    Node n1;
    Node n2;
    Node n3;
    n1.Node::Node(n);
    n2.Node::Node(n);
    n3.Node::Node(n);
}

参数初始化(Argument initialization)

        C++的标准中指出,当一个类对象以传值的形式作为函数的参数时,其等同于以下的形式:

Node n = args;

其中n是形参,args是实参。那么对于以下函数:

void func(Node n);

经转换之后,其伪代码如下:

Node n;

Node _temp_node;
_temp_node.Node::Node(n);
func(_temp_node); // void func(Node &);

 此时应该注意,func的参数已经由传值改变为传引用。

返回值的初始化(Return value initialization)

        对于函数func,其定义为:

Node func()
{
    Node n;
    return n;
}

那么函数的返回值是如何从局部对象n拷贝而来的呢?在cfront中,这一过函数返回值的初始化过程可以分为两步,1. 为该函数增加一个引用类型的参数,该参数持有该函数的返回值; 2. 调用该类的拷贝构造函数以初始化该参数。经过转换之后,该函数的伪代码如下:

Node _ret_node;

void func(Node& _ret_node)
{
    Node n;
    _ret_node.Node::Node(n);
    return ;
}

编译器级别的优化(Optimization at the compiler level)

        我们上面看到的这种优化函数返回值的方式被称为具名返回值(Named Return Value, NRV)优化。这种优化方式在C++中是一种强制优化措施(虽然标准并没有这样要求)。这种优化方式相对于直接返回值然后进行bitwise copy的效率会更高一些。但这种优化要求该类必需具有拷贝构造函数。

        拷贝构造函数有时候也会带来一些性能开销。例如,对于以下初始化方式:

Node n(1);

Node n1 = Node(1);

Node n2 = (Node)1;

对于第一种定义,n是通过构造函数直接初始化,而n1和n2的初始化方式则会产生一个临时对象,并且通过调用拷贝构造函数之后,还需要调用该临时对象的析构函数。这里就多个一个析构的开销。但是否应该取消拷贝构造函数呢?在作者写这本书时,标准委员会还没有定论,因为拷贝构造函数显然太重要了,不能轻易取消。

拷贝构造函数:要还是不要(Copy constructor: to have or not have) 

        对于下面这个类来说,它是否需要拷贝构造函数?

class Point3d
{
public:
    Point3d(float x, float y, float z) : m_x(x), m_y(y), m_z(z) {}
    float m_x;
    float m_y;
    float m_z;
};

对类Point3d来说,其没有显式定义的拷贝构造函数,也不满足编译器合成拷贝构造函数的四个条件之一,所以其没有拷贝构造函数。虽然没有拷贝构造函数,但是按位拷贝能够很好的满足程序的需求,按位拷贝不会造成内存的泄漏或者内存的重叠。所以没有拷贝构造函数的话,也没有性能或者安全性上的损失。

        那么该类真的就不需要拷贝构造函数了吗?如果该类对象需要大量的初始化操作,尤其是作为函数的返回值时,此时就应该考虑给该类声明一个拷贝构造函数。声明拷贝构造函数之后,编译器就能够进行NRV优化了。

总结

        拷贝构造函数能够在一定程度上优化程序的效率,尤其是NRV优化。要利用NRV优化,需要判断一个类是否有拷贝构造函数(编译器合成的或者显式声明的)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值