目录
前言
一.认识pair
二.创建一个mypair对象
二.用mypair对象去初始化另一个mypair对象
三.全局的函数模版make_pair
四.map中的insert函数
五.验证
前言
当一个类的成员变量有自定义类型时,我们可以考虑将构造函数设为模板,这样会带来不少便利。便利之处在哪呢?看完本文就明白了。
一.认识pair
pair的介绍
pair是C++标准库里的一个类模板,本文将会仿照pair,以自己实现的mypair为例来说明将函数模板作为拷贝构造函数的好处。
二.创建一个mypair对象
#include <string>
template<class first_type, class second_type>
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
first_type _first;
second_type _second;
};
int main()
{
mypair<string, string> m1;
return 0;
}
如上,调用默认构造函数创建了一个mypair<string, string>对象,但这并不能使得我们满足,我们想要根据自己的想法来初始化_first和_second。于是,增加一个构造函数:
#include <string>
template<class first_type, class second_type>
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
mypair(const first_type& first, const second_type& second)
:_first(first)
,_second(second)
{}
first_type _first;
second_type _second;
};
int main()
{
string s1 = ”left“;
string s2 = "左边";
mypair<string, string> m1(s1, s2);
return 0;
}
可以按照自己的想法初始化mypair<string, string>对象了,但是这样有点麻烦啊,还要先创建两个string对象,有没有更简便的写法呢?
当然可以使用匿名对象:
mypair<string, string> m1(string(“left"), string("左边"));
但更简洁的写法是这样:
mypair<string, string> m1("左边", "left“);
为什么可以这么写?因为传入的两个实参类型都是const char*, 而形参类型却是string的引用,string和const char*不匹配,所有这里会发生隐式类型转换,中间生成一个临时对象。正是因为const char*类型的变量能够拷贝构造string对象,这里的隐式类型转换才能顺利完成,所以实际上形参引用的是用const char* 拷贝构造的临时对象
二.用mypair对象去初始化另一个mypair对象
template<class first_type, class second_type>
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
mypair(const first_type& first, const second_type& second)
:_first(first)
,_second(second)
{}
mypair(const mypair<first_type, second_type>& pr)
:_first(pr._first)
,_second(pr._second)
{
cout << "mypair的拷贝构造或普通构造" << endl;
}
first_type _first;
second_type _second;
};
int main()
{
mypair<const string, string> m1("left", "左边");
mypair<string, string> m2("right", "右边");
mypair<const string, string> m3(m1);//用m1拷贝构造m3
//mypair<const string, string> m3(m2); 类型不匹配,编译报错
return 0;
}
只需在pair中添加一个拷贝构造即可,但是你会这样的拷贝构造太有局限性了,要求实参与形参的类型严格匹配。例如我有一个pair<string, string>对象,现在我想用它拷贝构造一个对象,并要求第一个成员变量不能更改,所以将第一个模版参数设为const string。但遗憾的是,这是不允许的,因为pair<string,const string>和pair<string, string>是两个不同的类型。
怎么解决这个问题呢?答案是使用函数模版
template<class first_type, class second_type>
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
mypair(const first_type& first, const second_type& second)
:_first(first)
,_second(second)
{}
template<class T1, class T2>
mypair(const mypair<T1, T2>& pr)
:_first(pr._first)
,_second(pr._second)
{
cout << "mypair的拷贝构造或普通构造" << endl;
}
first_type _first;
second_type _second;
};
int main()
{
mypair<string, string> m1("right", "右边");
mypair<const string, string> m2(m1);
return 0;
}
将拷贝构造函数设为模版,当你给它传传实参时,自动推演形参的模版参数,例如你传的是pair<string, string>对象,那么T1推演成string,T2推演成string,因为string能够拷贝构造const string,所以没问题。
甚至这样做都是允许的:
int main()
{
mypair<const string, string> m1(mypair<const char*, const char*>("left","左边"));
return 0;
}
这样能成功的原因是什么?因为const char*能初始化const string和string,这得多亏于string多样的构造函数。
小结:函数模版作为拷贝构造函数的优点在于,不必要求实参和被初始化的对象类型完全匹配,只需保证实参的模版参数类型能够用于初始化对象的成员变量即可。若实参类型和对象类型完全匹配,则生成的模版函数为拷贝构造函数,若不匹配,严格来说它只是一个普通构造函数。
三.全局的函数模版make_pair
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
mypair(const first_type& first, const second_type& second)
:_first(first)
,_second(second)
{}
template<class T1, class T2>
mypair(const mypair<T1, T2>& pr)
:_first(pr._first)
,_second(pr._second)
{
cout << "mypair的拷贝构造或普通构造" << endl;
}
first_type _first;
second_type _second;
};
template<class T1, class T2>
mypair<T1, T2> my_make_pair(T1 x, T2 y)
{
return mypair<T1, T2>(x, y);
}
int main()
{
mypair<const string, string> m1(my_make_pair("left","左边"));
mypair<const string, string> m2(mypair<const char*, const char*>("left","左边"))
}
仔细对比main函数中的两条语句,你会发现它们功能是一样的,但明显第一句更简洁。因为make_pair函数会帮我们自动推演pair的模版参数,不需要我们自己显式地写出来了。
四.map中的insert函数
如图,这是map的insert函数的声明,我们来关注它的参数。value_type是typedef过的,它其实是一个pair
typedef pair<const first_type, second_type> value_type;
int main()
{
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
return 0;
}
有了前面的介绍,我们就能理解这里的用法了。
首先dict调用insert函数,形参类型为const pair<const string, string>&,而make_pair的返回值类型为pair<const char*, const char*>,由于pair<const string, string>和pair<const char*, const char*>类型不匹配,所以要发生隐式类型转换,中间生成一个临时变量。这里为什么能隐式类型转换?这得得益于pair拷贝构造函数的巧妙设计,const char*能初始化const string和string,所以没问题。
五.验证
template<class first_type, class second_type>
struct mypair
{
mypair()
:_first(first_type())
,_second(second_type())
{}
mypair(const first_type& first, const second_type& second)
:_first(first)
,_second(second)
{}
template<class T1, class T2>
mypair(const mypair<T1, T2>& pr)
:_first(pr._first)
,_second(pr._second)
{
cout << "mypair的拷贝构造或普通构造" << endl;
}
first_type _first;
second_type _second;
};
template<class T1, class T2>
mypair<T1, T2> my_make_pair(T1 x, T2 y)
{
return mypair<T1, T2>(x, y);
}
void func(const mypair<const string, string> s)
{
cout << "func调用" << endl;
}
int main()
{
cout << "类型完全匹配,不会发生隐式类型转换:" << endl;
mypair<const string, string> m1("left", "左边");
func(m1);
cout << "--------------------------------------------" << endl;
cout << "类型不匹配,发生隐式类型转换,调用mypair的拷贝构造函数,生成临时对象:" << endl;
func(my_make_pair("left", "左边"));
return 0;
}