我的情况如下:
我有一个模板包装器,可以处理值和对象可以为空的情况,而无需手动处理指针甚至new
。这基本归结为:
struct null_t
{
// just a dummy
};
static const null_t null;
template<class T> class nullable
{
public:
nullable()
: _t(new T())
{}
nullable(const nullable<T>& source)
: _t(source == null ? 0 : new T(*source._t))
{}
nullable(const null_t& null)
: _t(0)
{}
nullable(const T& t)
: _t(new T(t))
{}
~nullable()
{
delete _t;
}
/* comparison and assignment operators */
const T& operator*() const
{
assert(_t != 0);
return *_t;
}
operator T&()
{
assert(_t != 0);
return *_t;
}
operator const T&() const
{
assert(_t != 0);
return *_t;
}
private:
T* _t;
};
现在使用比较运算符,我可以检查null_t
虚拟对象,以便在实际尝试检索值之前查看它是否设置为null,或者将其传递给需要该值的函数并执行自动转换。
这节课已经很好地服务了我很长一段时间,直到我偶然发现了一个问题。我有一个包含一些结构的数据类,这些结构将全部输出到一个文件(在本例中为XML)。
所以我有这些功能
xml_iterator Add(xml_iterator parent, const char* name,
const MyDataStruct1& value);
xml_iterator Add(xml_iterator parent, const char* name,
const MyDataStruct2& value);
每个用适当的数据填充XML-DOM。这也可以正常工作。
但是,现在,其中一些结构是可选的,在代码中将声明为
nullable<MyDataStruct3> SomeOptionalData;
为了处理这种情况,我制作了一个模板重载:
template<class T>
xml_iterator Add(xml_iterator parent, const char* name,
const nullable<T>& value)
{
if (value != null) return Add(parent, name, *value);
else return parent;
}
在我的单元测试中,编译器按预期始终优先选择此模板函数,只要将值或结构包装在nullable<T>
中。
但是,如果我使用上述数据类(在其自己的DLL中导出),由于某种原因第一次调用最后一个模板函数,而是从nullable<T>
自动转换为相应的类型T
已完成,完全绕过了处理此案例的函数。正如我上面所说 - 所有单元测试都达到了100%,测试和调用代码的可执行文件都是由MSVC 2005在调试模式下构建的 - 这个问题绝对不能归因于编译器差异。
更新:澄清一下 - 重载的Add
函数不会被导出,只能在DLL内部使用。换句话说,遇到此问题的外部程序甚至不包括具有模板重载功能的头部。
答案 0 :(得分:0)
编译器在找到模板化版本之前将主要选择完全匹配,但会选择与另一个适合的函数(例如,使用您的类型的基类的函数)进行模板化的“完全匹配”。
隐含的转换是危险的,经常会咬你。它可能只是包含您的标题或您正在使用的命名空间。
我会做以下事情:
使Nullable的构造函数全部显式化。您可以使用任何只接受一个参数的构造函数,或者可以使用一个参数调用(即使有更多具有默认值的构造函数)。
template<class T> class nullable
{
public:
nullable()
: _t(new T())
{}
explicit nullable(const nullable<T>& source)
: _t(source == null ? 0 : new T(*source._t))
{}
explicit nullable(const null_t& null)
: _t(0)
{}
explicit nullable(const T& t)
: _t(new T(t))
{}
// rest
};
更换操作员T&amp;使用命名函数进行转换。对于非常量使用ref(),对const使用cref()。
我也会用
完成课程如果你计划将它用于C ++ 0x,还要使用r值复制和赋值,这在这种情况下很有用。
顺便说一句,您确实知道您的深层副本不适用于基类,因为它们会切片。
答案 1 :(得分:0)
好吧,因为到目前为止还没有找到真正的答案,我已经做了一个解决方法。基本上,我将前面提到的Add
函数放在一个单独的detail
命名空间中,并添加了两个模板包装函数:
template<class T>
xml_iterator Add(xml_iterator parent, const char* name,
const T& value)
{
return detail::Add(parent, name, value);
}
template<class T>
xml_iterator Add(xml_iterator parent, const char* name,
const nullable<T>& value)
{
return value != null ? detail::Add(parent, name, *value) : parent;
}
我发现这总是正确地解析为这两个函数中正确的一个,并且将在这些函数中单独选择实际包含类型的函数,如您所见。