2

我的情况如下:

我有一个模板包装器,它可以处理值和对象可以为空的情况,而无需手动处理指针甚至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 内部使用。换句话说,遇到这个问题的外部程序甚至不包括带有模板重载函数的头部。

4

2 回答 2

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& 转换。对非常量使用 ref(),对 const 使用 cref()。

我也会完成这门课

  • 赋值运算符(规则 3 需要)
  • 运算符-> 传播常量时的两个重载。

如果您打算将其用于 C++0x,则还需要 r-value 复制和分配,这在这种情况下很有用。

顺便说一句,您确实知道您的深层副本不适用于基类,因为它们会切片。

于 2011-01-25T09:47:20.143 回答
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;
    }

我发现这总是能正确解析这两个函数中的一个,并且实际包含类型的函数将在这些函数中的单独步骤中选择,如您所见。

于 2011-02-22T14:06:14.617 回答