文章向导
问题引入
代码实现
Bug分析(error: no matching function for call to…) 与编译器的行为
修正后的代码
一、问题引入
~~~~
~~~
实现如下图所示几个类,它们之间存在一定的组合或继承关系:
二、代码实现
#include <iostream>
#include <sstream>
using namespace std;
class Object
{
protected:
string mName;
string mInfo;
public:
Object()
{
mName = "Object";
mInfo = "";
}
string name()
{
return mName;
}
string info()
{
return mInfo;
}
};
class Point : public Object
{
private:
int mX;
int mY;
public:
Point(int x, int y)
{
mX = x;
mY = y;
mName = "Point";
ostringstream oss;
oss << "P(" << mX << ", " << mY << ")";
mInfo = oss.str();
}
int x()
{
return mX;
}
int y()
{
return mY;
}
};
class Line : public Object
{
private:
Point mP1;
Point mP2;
public:
Line(Point p1, Point p2)
{
mP1 = p1;
mP2 = p2;
mName = "Line";
ostringstream oss;
oss << "Line from" << mP1.info() << " to " << mP2.info();
mInfo = oss.str();
}
};
int main()
{
Object o;
Point p1(1,2);
Point p2(3,4);
Line l(p1, p2);
cout << o.name() << endl;
cout << o.info() << endl;
cout << p1.name() << endl;
cout << p1.info() << endl;
cout << l.name() << endl;
cout << l.info() << endl;
return 0;
}
[程序结果]
~~~~
~~~
如上图所示,编译后并未按预期打印出三个对象的信息,反而编译器提示了一堆错误信息。别急!这也是咱们今天需要分析的主角:error: no matching function for call to …。通过它以理解编译器的行为,从而指导后续面向对象程序的设计。
三、Bug分析?
~~~~
~~~
根据编译器的提示信息,笔者将涉及到的相关代码都整合在一起(位于下图),以便对照分析和理解。
~~~~ ~~~ 编译器提示我们:此时没办法匹配到无参构造函数Point(),因为候选者只有含参构造Point(int, int)以及拷贝构造Point(cosnt Point&)。Wait? ①为啥要提供无参构造? ②自己也没有编写拷贝构造的相应代码吖,为啥提示已存在Point(cosnt Point&)?
~~~~
~~~
要回答上面两个疑问,首先需理解编译器在类中的默认行为有哪些,这里有一些规则需要读者理解并记忆:
- 当类中不存在构造函数时,编译器会默认提供一个无参构造函数(其函数体为空)。
- 当类中不存在拷贝构造函数时,编译器会默认提供一个浅拷贝构造函数。
- 编译器为每个类默认重载了赋值操作符(但实现的是浅拷贝)
- 编译器会默认为类提供一个析构函数,但当类中自定义了构造函数,且构造函数中使用了系统资源(如内存申请,文件打开等),则需要自定义析构函数。
~~~~
~~~
有了上述几条规则,疑问②自然也就解决。可疑问①应如何理解并解决呢? 此时我们不妨按照之前的错误提示,将代码修改为(为Point类添加无参构造函数,其函数体为空):
[程序结果]
~~~~
~~~
从打印结果来看,问题貌似已经得到了解决。可事实果真如此吗? 笔者继续将代码做一丁点改动(去掉刚才添加的无参构造函数,并对main函数中的p1做出调整):
[程序结果]
~~~~
~~~
此时,类似的错误信息反而在main函数中也出现了。聚焦至Point p1;
这条语句(该句确实没有给p1提供初始化时的参数,自然也就需要调用无参构造函数),大家已经快接近疑问①的真相了。
~~~~
~~~
重新回到最初的这张图,并将视线聚焦于Line类中的成员对象mP1和mP2,需注意的是此时书写的Point mP1;Point mP2只是成员对象的声明,而非定义。只有在Line构造函数执行时,这两个成员对象才会被真正定义出来。可现在面临的现状是:我们是以无参的形式来声明mP1, mP2,而此时可选的构造函数仅有Point(int, int)和Point(const Point&),故自然就会提示“no matching function for call to…”这样的错误信息。
四、修正后的代码
推荐的方式:
~~~~
~~~
该方式不仅解决了Line类中成员对象mP1和mP2的声明问题,同时也解决了main函数中类似于Point p1;
这样的书写形式所引发的构造问题。
~~~~
~~~
另外,需要注意的是,在类中初始化和赋值两者的底层效率是不一样的(前者直接用相应值来初始化数据成员,后者则先初始化再赋值),故建议读者用初始化列表来初始化成员变量。同时,对于本问题也可以提炼出一个重要的结论:当成员变量属于某种类类型且该类没有定义默认构造函数时,必须将该成员初始化(可利用初始化列表的方式)。
参阅资料
狄泰软件学院C++深度解析教程
primer cpp 第5版
C++ 标准库 第2版