• C++编译器自动生成的函数(Effective C++之05)


      在C++中当创建一个空类时,C++就会默认的为这个类创建4个函数:默认的构造函数、析构函数、拷贝构造函数、以及赋值操作符。本文参考Effective C++介绍这几个函数。

    目录:

    1. 函数的原型以及创建函数的时机

    2. 赋值操作符存在的问题

    3. C++0x的新变化


    1. 函数的原型以及函数创建的时机

    C++中创建一个空类:

    class Empty {};

    默认会生成4个函数,其函数的原型如下:

    public:
        Empty() { ... }
        Empty(const Empty& rhs) { ... }
        ~Empty() { ... }
        Empty& operator=(const Empty& rhs) { ... }

    说明:1) 这些函数只有在需要调用的时候,编译器才会生成。2) 4个函数都是public的。3) 4个函数都是inline的(即函数定义在类的定义中的函数)。4) 如果你显式的声明了这些函数中的任何一个函数,那么编译器将不再生成默认的函数。

    比如,当遇到下列语句时,函数会被编译器生成:

    Empty e1;                //默认构造函数
                             //对象销毁时,析构函数
    Empty e2(e1);            //拷贝构造函数
    e2 = e1;                 //赋值运算符

    另外,还存在两种默认的函数:就是取地址运算符和取地址运算符的const版本,这两个函数在《Effective C++》中没有提及。

    public:
        Empty* operator&() { ... }
        const Empty* operator&() const { ... }

    这两个函数是确实存在的,正如下面的代码可以正常工作:

    #include <stdio.h>
    
    class Empty {
    };
    
    int main(int argc, char** argv)
    {
            Empty a;
            const Empty *b = &a;
            printf("%p\n", &a);             //调用取地址运算符
            printf("%p\n", b);              //调用const取地址运算符
    }

    一个容易被忽略的问题:自定义的拷贝构造函数不仅会覆盖默认的拷贝构造函数,也会覆盖默认的构造函数。下面的代码是编译不过的,用户必须再显式的定义一个无参的构造函数。

    class Empty {
    public:
        Empty(const Empty& e) { }    //拷贝构造函数
    };
    
    int main(int argc, char** argv)
    {
        Empty a;
    }

    2.  赋值操作符存在的问题

    赋值操作符函数的行为与拷贝构造函数的行为基本是相同的,编译器生成赋值操作符函数是有条件的,如果会产生无法完成的操作,编译器将拒绝产生这一函数。那么什么时候编译器无法完成赋值这一行为呢?考虑如下情形(来源Effective C++):

    template<class T>
    class NameObject {
    public:
        NameObject(std::string& name, const T& value);
    private:
        std::string& nameValue;    //引用成员变量
        const T objectValue;       //const成员变量
    };

     然后考虑下面的语句会发生什么事:

    std::string newDog("abc");
    std::string oldDog("xxx");
    NameObject<int> p(newDog, 2);
    NameObject<int> s(oldDog, 10);
    p = s;                           //将会发生什么?

    赋值语句之前,p.nameValue指向newDog, s.nameValue指向oldDog。那么赋值之后呢?p.nameValue应该指向s.nameValue指向的对象吗?但是C++有一条规定:引用不能改指向另外一个对象。

    对于变量objectValue,C++规定:更改const成员是不合法的。

    因此如果上面两种情形中的任何一种发生了,C++编译器给出的响应是:拒绝编译这一行的赋值动作。如果你这么做了,C++编译器会报错。如果你执意要进行赋值操作,那么可以自己定义一个赋值操作符重载函数。

    3. c++0x中的新变化

    C++0x中引入了“右值引用”和“移动语义”的概念,可以实现对右值的引用。(左值和右值的解释可以见http://amyz.itpub.net/post/34151/411832

    移动语义,简单来说,就是在一个右值对象的生命期结束之前,将其资源偷过来,为我所用。有关移动语义的详细内容这里不做详述,大家可以参见csdn上一篇文章 http://blog.csdn.net/pongba/article/details/1684519。这里要说明的是移动构造函数和移动赋值运算符。

    1. 移动构造函数和移动赋值运算符重载函数不会隐式声明,必须自己定义。

    2. 如果用户自定义了拷贝构造函数或者移动构造函数,那么默认的构造函数将不会隐式定义,如果用户需要,也需要显式的定义

    3. 移动构造函数不会覆盖隐式的拷贝构造函数。

    4. 移动赋值运算符重载函数不会覆盖隐式的赋值运算符重载函数。

  • 相关阅读:
    POJ 1401 Factorial
    POJ 2407 Relatives(欧拉函数)
    POJ 1730 Perfect Pth Powers(唯一分解定理)
    POJ 2262 Goldbach's Conjecture(Eratosthenes筛法)
    POJ 2551 Ones
    POJ 1163 The Triangle
    POJ 3356 AGTC
    POJ 2192 Zipper
    POJ 1080 Human Gene Functions
    POJ 1159 Palindrome(最长公共子序列)
  • 原文地址:https://www.cnblogs.com/xiaoxinxd/p/effective_cpp_05.html
Copyright © 2020-2023  润新知