专业编程基础技术教程

网站首页 > 基础教程 正文

Effective C++读书之拷贝构造函数和成员变量初始化

ccvgpt 2024-10-10 05:02:37 基础教程 9 ℃

构造函数初始化成员变量

构造函数初始化成员变量有两种方法,一种是通过在构造函数中赋值的方式,另外一种是通过成员初始化列表的方式,两者初始化方式最大的差别就是后者比前者效率高性能好。这是因为C++在运行过程中首先调用成员变量的默认构造函数,再进入构造函数中。我们拿实际代码来观察一下。

#include <iostream>
#include <list>

class Object{
public:
    Object();
    ~Object() = default;

    const class Object &operator=(const class Object &Rhs);
    bool operator==(const class Object &Rhs);

private:
    int Value;
};

Object::Object():Value(0)
{
    std::cout << "Object constructor run" << std::endl;
}

bool Object::operator==(const class Object &Rhs)
{
    if(this->Value == Rhs.Value){
        return true;
    }
    return false;
}

const class Object& Object::operator=(const class Object &Rhs)
{
    std::cout << "Object operator= run" << std::endl;
    if(*this == Rhs){
        return *this;
    }
    this->Value = Rhs.Value;
    return *this;
}

class PthoneNumber{
public:
    PthoneNumber() = default;
    ~PthoneNumber() = default;

private:
    std::list<int> TelphoneNumbers;
};

class ABEntry{
public:
    ABEntry(const std::string &Name, const std::string &Address,
            const std::list<PthoneNumber> &Pthones, const Object &InputObject);
    ~ABEntry() = default;

private:
    std::string Name;
    std::string Address;
    std::list<PthoneNumber> ThePhones;
    int TheTimesConsult;
    class Object EmptyObject;
};

ABEntry::ABEntry(const std::string &Name, const std::string &Address,
                const std::list<PthoneNumber> &Pthones, const Object &InputObject)
:Name(Name),
Address(Address),
ThePhones(Pthones),
EmptyObject(InputObject),
TheTimesConsult(0)
{
    std::cout << "ABEntry constructor run" << std::endl;
    //EmptyObject = InputObject;
}

int main(int argc, char *argv[])
{
    std::string Name("Lihua");
    std::string Addr("Beijing");
    std::list<PthoneNumber> Pthones;
    Object TempObject;

    ABEntry AddrBookEntry(Name, Addr, Pthones, TempObject);

    return 0;
}

在类ABEntry实现的构造函数中使用成员初始化列表的方式初始化成员变量EmptyObject,而我们在Object类的构造函数和ABEntry构造函数中分别打印一条消息,现在我们编译运行看看结果。

Effective C++读书之拷贝构造函数和成员变量初始化

从输出结果中可以看出在进入ABEntry构造函数之前就已经对EmptyObject成员变量进行了初始化,所以通过成员变量初始化列表初始化成员变量是在进入构造函数之前进行。

我们再来看一下通过在构造函数里面通过赋值的方式对成员变量进行初始化的效果,以此和成员变量初始化列表进行对比。因为Emptyobject成员变量的构造函数中有输出信息,只要把Emptyobject通过赋值的方式初始化即可看到效果,我们把ABEntry构造函数修改如下。

ABEntry::ABEntry(const std::string &Name, const std::string &Address,
                const std::list<PthoneNumber> &Pthones, const Object &InputObject)
:Name(Name),
Address(Address),
ThePhones(Pthones),
//EmptyObject(InputObject),
TheTimesConsult(0)
{
    std::cout << "ABEntry constructor run" << std::endl;
    EmptyObject = InputObject;
}

现在我们重新编译运行看看效果。

从输出结果我们可以看到总共调用了2次Object构造函数,和1次Object赋值运算符,而且赋值运算符在构造函数之后运行。我们的代码里只有在main函数里面定义了1个Object类型变量TempObject会调用1次默认的构造函数,那么多出的1次调用构造函数肯定是在调用ABEntry构造函数时进行的,也就说明了一个问题,C++先调用成员变量的默认构造函数再进入构造函数内进行赋值操作初始化成员变量,所以赋值初始化成员变量和成员变量初始化列表的最大不同点是:在构造函数中通过赋值操作初始化成员变量,会先调用成员的默认构造函数再通过赋值初始化成员变量,比初始化成员列表的方式多调用1次构造函数和赋值运算符,因此成员变量初始化列表比赋值操作初始化效率高、性能好。

如何创建一个不可拷贝的类

在我们没有声明一个拷贝构造函数和赋值运算符时,C++编译器会帮我们生成一个默认的拷贝构造函数和赋值运算符,且生成的拷贝构造函数和赋值运算符是公有的。如果我们想创建一个不可拷贝的类该如何做呢?常见的方法有:1、将拷贝构造函数和赋值运算符变成私有的,2、拷贝构造函数和赋值运算符变成受保护的,3、类继承于一个不可拷贝的父类,该父类没有任何属性,只将拷贝构造函数和赋值运算符声明为私有的。

我们先来看看第一种方法:将拷贝构造函数和赋值运算符变成私有的。这种情况看起来类无法拷贝,但是C++的友元函数可以访问类的私有属性和方法,因此也无法避免类被拷贝。再来看第二种方法:拷贝构造函数和赋值运算符变成受保护的。和第一种比较像,友元函数也是可以访问受保护的属性和方法,而且子类可以访问受保护的属性和方法,因此也是无法避免类被拷贝。而第三种方法:类继承于一个不可拷贝的父类,该父类没有任何属性,只将拷贝构造函数和赋值运算符声明为私有的。在C++中调用拷贝构造函数不仅拷贝子类部分还会拷贝父类部分,而拷贝父类部分则需要父类的拷贝构造函数是公有的或者受保护的。因此,如果父类的拷贝构造函数和赋值运算符是私有的,那么在子类中的任何方法和属性都无法调用父类的拷贝构造函数和赋值运算符,误用的情况下,在编译器在编译期间就会报错。我们以实际代码作为例子。

#include <iostream>

class Uncopyable{
protected:
    Uncopyable() = default;
    ~Uncopyable() = default;

private:
    Uncopyable(const Uncopyable&){}
    Uncopyable& operator=(const Uncopyable&){}
};

template<typename T>
class Object : private Uncopyable{
public:
    Object(const T& Value);
    ~Object() = default;

private:
    T& ObjectValue;
};

template<typename T>
Object<T>::Object(const T& Value):ObjectValue(Value) {}

int main(int argc, char *argv[])
{
    Object<int> NewObject(10);
    Object<int> OldObject(20);

    NewObject = OldObject; //错误!!!无法调用Uncopyable的赋值运算法
    Object<int> TempObject(OldObject); //错误!!无法调用Uncopyable的拷贝构造函数

    return 0;
}

在写C++代码尽量用成员列表初始化的方式初始化成员变量,增加效率和性能。在某些情况下,需要我们创建一个不可拷贝的类,则将类继承于拷贝构造函数和赋值运算符被声明为私有的父类。

最近发表
标签列表