zoukankan      html  css  js  c++  java
  • 深入理解c++构造函数, 复制构造函数和赋值函数重载(operator=)

    以下代码编译及运行环境均为 Xcode 6.4, LLVM 6.1 with GNU++11 support, Mac OS X 10.10.2

    调用时机

    看例子

    //
    //  main.cpp
    //  test
    //
    //  Created by dabao on 15/9/30.
    //  Copyright (c) 2015年 Peking University. All rights reserved.
    //
    
    #include <iostream>
    
    class Base
    {
    public:
        Base()
        {
            std::cout<<"constructor"<<std::endl;
        }
        
        Base(Base &copy)
        {
            std::cout<<"copy constructor"<<std::endl;
        }
        
        const Base &operator=(Base &copy)
        {
            std::cout<<"operator="<<std::endl;
            return *this;
        }
    };
    
    int main(int argc, const char * argv[])
    {
    
        Base a;     // 1
        Base b = a;  // 2
        Base c(a);   // 3
        
        Base d;       // 4
        d = a;
        
        return 0;
    }
    

      输出

    constructor
    copy constructor
    copy constructor
    constructor
    operator=
    

      1,2,3,4 是我们创建一个变量的最主要的方法(构造序列本文不讨论), 其中1,2,3是变量定义, 4是赋值. 因此很明显:

    1. 定义会调用构造函数, 赋值会调用赋值函数(operator=)
    2. 复制构造函数是一种特殊的构造函数, 参数是一个变量实例而已
    3. 2和3等价, 3不会调用赋值函数(手误) 2不会调用赋值函数, 出现等号未必就是赋值
    4. 如果没有重载以上函数, 3和4效果会一样, 但会少一次函数调用

     const来捣乱

    那么const又起到什么作用了呢?

    继续来看例子

    //
    //  main.cpp
    //  test
    //
    //  Created by dabao on 15/9/30.
    //  Copyright (c) 2015年 Peking University. All rights reserved.
    //
    
    #include <iostream>
    
    class Base
    {
    public:
        Base()
        {
            std::cout<<"constructor"<<std::endl;
        }
        
        Base(Base &copy)
        {
            std::cout<<"copy constructor"<<std::endl;
        }
        
        const Base &operator=(Base &copy)
        {
            std::cout<<"operator="<<std::endl;
            return *this;
        }
    };
    
    Base creator()
    {
        Base ret;
        return ret;
    }
    
    int main(int argc, const char * argv[])
    {
    
        Base a = creator();    // 1
        
        Base b;
        b = creator();     // 2
        
        return 0;
    }
    

      上述代码都会编译出错, 原因是 "No matching constructor". 看代码不难发现原因, creator函数返回的是Base类型, 在c++11里面, 这个称为右值(rvalue), 但是我们的复制构造函数和赋值函数的参数类型都是非const引用类型, 而右值是不允许做这种类型参数的, 所以就编译出错了. 解决方案有两个:

    1. 使用const引用类型
    2. 使用右值类型

    如下所示

        Base(const Base &copy)
        {
            std::cout<<"copy constructor"<<std::endl;
        }
        
        const Base &operator=(Base &&copy)
        {
            std::cout<<"operator="<<std::endl;
            return *this;
        }
    

      其中, const引用类型是最通用的作法, 它可以兼容左值和右值, 也兼容古老的编译器, 右值类型则是c++11引进的新特性(使用&&表明), 可以针对左值和右值选择不同的实现, 比如使用std::move替代operator=, 从而减少内存的申请. 因此, 如果没有特殊需要, 使用const引用类型作为复制构造函数与赋值函数的参数类型.

    至此, 构造函数的坑基本说完了, 因为不牵扯到返回值和函数类型的问题, 但是赋值函数(operator=)还有更多的坑来理一理.

    const继续搅局

    在一个类的成员函数中, const可以出现三个地方: 返回值, 参数, 函数.

    const A& operator=(const A& a) const
    

    因此一个函数可以有8个变种, 但是c++不允许参数类型相同,返回值类型不同的重载, 因此一个函数最多有4种实现. 

    我们先考虑返回const类型的情况

    //
    //  main.cpp
    //  test
    //
    //  Created by dabao on 15/9/30.
    //  Copyright (c) 2015年 Peking University. All rights reserved.
    //
    
    #include <iostream>
    
    class A
    {
    public:
        const A& operator=(const A& a) const
        {
            std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(const A& a)
        {
            std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(A& a) const
        {
            std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(A& a)
        {
            std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        std::string x;
        
        A() : x(""){}
        A(std::string x_) : x(x_) {}
    };
    
    int main(int argc, const char * argv[])
    {
    
        A a("a"), b("b");
        const A c("const c"),d("const d");
        
        c = d;
        c = b;
        a = d;
        a = b;
        
        return 0;
    }
    

     输出结果

    const A& operator=(const A& a) const [const d > const c]
    const A& operator=(A& a) const [b > const c]
    const A& operator=(const A& a) [const d > a]
    const A& operator=(A& a) [b > a]
    

    结果很明显, 被赋值变量决定函数, 赋值变量决定参数, a=b 等价于 a.operator(b), 这里没什么问题.

    但是, 有一个很奇怪的地方, a=d 这一句, a是非const的, 调用了 const A& operator=(const A& a) [const d > a], 返回值是个const类型, 这怎么可以呢? 返回值的const是什么意思呢? 这是非常有迷惑性的. 这个问题的关键点在于:

    a是这个函数的一部分, 并不是返回值的承接者. 因此 a=d 实际上是等价于 const A& ret = a.operator=(d), 也就是说, operator=的返回值类型和被赋值的变量是没有任何关系的! 

    加入以下代码

        const A &m = (a = d);  // 1
        A &n = (a = d);      // 2
    

    2会编译错误, 原因就在于把 const A& 绑定给 A&, 这肯定是错误的. 因此再重复一遍, operator=的返回值和被赋值变量没有任何关系.

    那么返回值有什么意义呢? 这就和iostream类似了, 是为了进行串联赋值, 亦即 a=b=c

    来看最后的例子

    //
    //  main.cpp
    //  test
    //
    //  Created by dabao on 15/9/30.
    //  Copyright (c) 2015年 Peking University. All rights reserved.
    //
    
    #include <iostream>
    
    class A
    {
    public:
        const A& operator=(const A& a) const
        {
            std::cout<<"const A& operator=(const A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(const A& a)
        {
            std::cout<<"const A& operator=(const A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(A& a) const
        {
            std::cout<<"const A& operator=(A& a) const ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        const A& operator=(A& a)
        {
            std::cout<<"const A& operator=(A& a) ["<<a.x<<" > "<<x<<"]"<<std::endl;
            return *this;
        }
        
        std::string x;
        
        A() : x(""){}
        A(std::string x_) : x(x_) {}
    };
    
    int main(int argc, const char * argv[])
    {
    
        A a("a"), b("b");
        
        const A c("const c"),d("const d");
        
        (a = b) = c;    // 1
        
        (a = c) = b;    // 2
        
        a = b = c;     // 3
        
        return 0;
    }
    

    输出

    const A& operator=(A& a) [b > a]
    const A& operator=(const A& a) const [const c > a]
    const A& operator=(const A& a) [const c > a]
    const A& operator=(A& a) const [b > a]
    const A& operator=(const A& a) [const c > b]
    const A& operator=(const A& a) [b > a]
    

      

    可以得出如下结论:

    1. 1和3比较可以发现, 赋值的顺序是从右往左执行的
    2. 返回值是const类型, 那么再被赋值就会调用const函数了

    总结

    1. 复制构造函数和赋值函数出现在两种不同的场景里, 不是出现等号就会调用赋值函数
    2. 赋值函数的返回值和被赋值变量是完全独立的
  • 相关阅读:
    java中split()特殊符号"." "|" "*" "" "]"
    AJAX传递数组
    d3.js+svg的树形图
    d3.js之树形折叠树
    echarts之bootstrap选项卡不能显示其他标签echarts图表
    mysql 将时间戳与日期时间的转换
    ztree使用
    EL表达式之sessionScope
    struts2配置文件中的method={1}详解
    XML创建与解析常用方法介绍
  • 原文地址:https://www.cnblogs.com/dabaopku/p/4849377.html
Copyright © 2011-2022 走看看