托管 c++ 中的 Bug
看起来是在托管 C++ 中,使用非托管模板类/结构时出现的错误。
引言
看起来在托管 C++ 中,调用带有模板参数的虚成员函数时会出现错误。
示例
#include "stdafx.h"
#using <mscorlib.dll>
#include <tchar.h>
using namespace System;
template <class T>
struct xroot
{
xroot() {}
xroot(T t) {}
xroot(const xroot &x) {}
~xroot() {}
};
class CClass
{
public:
virtual void func(xroot<int> x) {}
};
/*
void fff() {
xroot<int> _a;
xroot<int> _b(_a);
}
*/
int _tmain(void)
{
CClass *c = new CClass();
c->func(5);
return 0;
}
注释
根据 C++ 规范,代码中的调用顺序应该是
c->func(5);
如下:
1. 调用构造函数 xroot<int>(int t)
2. 调用 c->func,并将刚构造的参数传递给它
3. 调用参数的析构函数
4. 从 c->func 返回
但在托管 C++ 项目中,顺序看起来像
1. 调用构造函数 xroot<int>(int t)
2. 调用刚构造的 xroot 的析构函数
3. 调用 c->func,并将刚构造的参数传递给它
4. 再次调用参数的析构函数
5. 从 c->func 返回
但实际上,对同一个 xroot 没有两次析构函数调用——而是存在第二个 xroot,它是由默认拷贝构造函数构造的。是的,我们有自己的拷贝构造函数,但由于编译器错误,它没有被实例化。相反,编译器实例化了默认构造函数。要检查这一点,请取消注释 fff() 函数——现在拷贝构造函数将被实例化,调用顺序将如下:
1. 调用构造函数 xroot<int>(int t)
2. 调用拷贝构造函数 xroot<int>(const xroot<int> &x),并将刚构造的 xroot 传递给它
3. 调用第一个 xroot 的析构函数
4. 调用 c->func,并将刚构造的参数传递给它
5. 调用第二个 xroot 的析构函数
6. 从 c->func 返回
这里有一些非常有趣的事情——这个问题仅在以下情况下存在:
1. 在托管 C++ 项目中(非托管 C++ .NET 运行良好)
2. 在模板类/结构中
3. 模板的构造函数将构造的参数作为虚成员函数的参数传递
4. 没有其他代码可以强制编译器实例化拷贝构造函数
结论
因此,实际上我们在托管 C++ 编译器中存在两个错误。
第一个错误——没有必要通过调用拷贝构造函数来创建参数的第二个实例——根据规范,应该将第一个实例发送到被调用的函数。
第二个错误——编译器没有实例化拷贝构造函数,而是使用了默认构造函数。