C/C++编程:了解C++默默编写并调用了哪些函数

1059 篇文章 286 订阅

面试题:对于一个类,C++默认会生成哪些函数呢?

在C++中一共有8个默认构造函数:

  • 默认构造函数、默认析构函数:
  • 默认拷贝构造函数、默认拷贝运算符(即重载赋值运算符)
  • 默认移动构造函数、默认移动运算符(重载移动赋值操作符函数)
  • 默认重载取地址运算符const函数、默认重载取地址运算符非const函数

什么时候空类不再是个空类呢?当C++处理过它之后。定义为空类之后,默认会生成如上8个函数

  • 所有这些函数都是public而且inline,只有当这些函数被调用,它们才会被编译器创建出来。
  • 编译器产生出的析构函数是non-virtual的,除非这个类的基类自身声明由virtual析构函数
  • 如果没有显式定义,编译器会自动生成默认的重载取地址运算符函数,函数内部直接 ruturn this;
  • 默认构造函数和析构函数主要是给编译器一个地方用来放置"藏身幕后"的代码,像是调用基类和non-static成员变量的构造函数和析构函数。
  • 对于拷贝函数和拷贝运算符,编译器创建的版本只是单纯的将来自元对象的每一个non-static成员变量拷贝到模板对象。

也就是说:

class A{};

相当于:

class A
{
public:
    A();                            // 默认构造函数
    ~A();                           // 默认析构函数
    A(const A&);                    // 默认拷贝构造函数
    A& operator = (const A&);       // 默认重载赋值运算符函数
    A* operator & ();               // 默认取地址运算符函数
    const A* operator & () const;   // 默认重载取地址运算符const函数
    A(A&&);                         // 默认移动构造函数
    A& operator = (const A&&);      // 默认重载移动赋值操作符函数
};

编程范式:如果不想使用编译器自动生成的函数,就应该明确拒绝

一般来讲,如果你不希望类支持某一特定功能,只要不声明对应函数就可以了。但是这个策略对拷贝构造函数和拷贝运算符这样的不起作用,因为当某些人尝试调用它们,编译器就会自动生成这些函数并且是public的

解决方法:

  • 第一种方法:可以将相应的成员函数声明为private并且不实现(不推荐)
    • 为什么要声明?阻止编译器创建拷贝函数/拷贝运算符等函数。
    • 为什么不要实现?因为成员函数和友元还是可以调用private函数,而我们只声明不定义,这时当有人调用就会获得一个链接错误
    • 为什么private权限:阻止其他人调用
  • 第二种方法:也可以使用类似Uncopyable 这样的基类(推荐)

第一种方法怎么写?

怎么解决呢? 可以

class HomeForSale{
private:
	HomeForSale(const HomeForSale&); //只有声明
	HomeForSale& operator=(const HomeForSale&)
}

第二种方法怎么写?

// Uncopyable是一个一个专门为了阻止拷贝动作而设计的基类,它可以将将链接期错误移动到编译期
class Uncopyable{
protectde:               //允许派生类对象构造和析构
	Uncopyable(){}
	~Uncopyable(){}
private:
	Uncopyable();  //但是阻止拷贝
	Uncopyable operator=(const Uncopyable&);
};


class HomeForSale : private Uncopyable {  //类中不再声明拷贝构造函数或者拷贝运算符

}

比起第一种方法的优点是:

  • 第一种方法 如果友元调用拷贝函数/拷贝运算符等函数时,会出现未定义错误
  • 而第二种方法,比如上面,只要任何地方尝试拷贝HomeForSale 对象,编译期就会试着生成一个拷贝构造函数或者拷贝运算符,这些函数的"编译期生成版本"会尝试调用其基类的对应兄弟,那些调用会被编译期拒绝,因为其基类的拷贝函数是private。

详解

默认生成的函数到底是干了些啥?

看个例子:

template<typename T>
class NamedObject{
public:
	NamedObject(const char* name, const T& value);
	NamedObject(const std::string& name, const T& value);
private:
	std::string nameValue;
	T objectValue;
};
  • 上面声明了一个构造函数,编译器就不会再为这个类创建默认构造函数
  • NameObject既没有声明拷贝构造函数,也没有声明拷贝运算符,所以编译器会为它创建那些函数(如果它们被调用的话)
NamedObject<int>no1("smaller", 2);
NamedObject<int>no2(no1); // 调用拷贝构造函数

编译器生成的拷贝函数必须以no1.nameValue和no1.objectValue为初值设置no2.nameValue和no2.objectValue:

  • nameValue的类型是string,而string有一个拷贝构造函数,所以no2.nameValue的初始化方式是调用string的拷贝构造函数并以no1.nameValue为实参。
  • objectValue的类型是int,所以no2.objectValue会以"拷贝no1.objectValue内的每一个bit"来完成初始化。

一般来说,只有当生成的代码合法而且由适当机会证明它有意义,才会生成operator=,否则编译器不会为类声明operator=。举个例子:

template<typename T>
class NamedObject{
public:
	//name不能是const,因为nameValue只接受reference-to-non-const string
	NamedObject(std::string& name, const T& value);
private:
	std::string &nameValue;  //是个引用
	const T objectValue;     //是个const
};

那么:

std::string newDog("pade");
std::string oldDog("lpds");

NamedObject<int> p (newDog, 2);
NamedObject<int> s (oldDog, 21);
p = s;   // p的成员变量将会发生什么事?

赋值之前,p.nameValue和s.nameValue指向两个不同的string对象。赋值之后呢?p.nameValue会指向s.nameValue吗?也就是说引用自身可被改动吗?

C++并不允许"让引用指向不同的对象",因此,C++会拒绝编译哪一行的赋值动作。也就是说不会生成拷贝运算符

如果你打算在一个"内含引用成员"的类支持拷贝操作,你必须自己定义拷贝运算符。

面对"内含引用成员"的类,编译器的反应也一样。更改const成员是不合法的,

最后,如果某个基类将拷贝运算符声明为private,编译器将拒绝为其派生类生成一个拷贝运算符。因为编译器为派生类所生成的拷贝运算符想象中可以处理基类成分,但是它们无法调用派生类无权调用的成员函数。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值