7

与使用一条语句相比,重载要采用true_type或参数的方法/函数有什么好处吗?我看到越来越多的代码使用带有和参数的重载方法。false_typeiftrue_typefalse_type

使用if语句的简短示例

void coutResult(bool match)
{
    if (match)
        cout << "Success." << endl;
    else
        cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);

与使用重载函数相比:

void coutResult(true_type)
{
    cout << "Success." << endl;
}

void coutResult(false_type)
{
    cout << "Failure." << endl;
}

bool match = my_list.contains(my_value);
coutResult(match);
4

3 回答 3

13

您的第二个示例代码无法编译,这是编译时重载决议运行时条件分支“选择”要执行的代码之间差异的症状。

  • 如果决定仅取决于类型和编译时常量,则“重载要获取true_type或参数的函数”允许在编译时false_type做出此选择。
  • 如果在某些变量值已知的运行时if才能进行选择,则“使用检查”是必要的。

在您的示例中,bool match = my_list.contains(my_value)在运行程序之前显然不知道,因此您不能使用重载。

但是区别对于模板来说是最重要的,在模板中选择不仅是“执行哪个路径”,而且是“实例化和编译然后调用哪个代码”。您链接的视频中的代码正是本着这种精神:

考虑这个(错误的)代码(为了简洁省略#includes 和std::s):

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    if (is_same<Tag, random_access_iterator_tag>::value)
    {
        return last - first;
    }
    else
    {
        Diff n = 0;
        while (first != last) {
            ++first;
            ++n;
        }
        return n;
    }
}

这里至少有两个问题:

  • 如果您尝试使用随机访问的迭代器(例如std::list<T>::iterator)调用它,它实际上将无法编译(错误指向行return last - first;)。编译器必须实例化和编译整个函数体,包括分支if分支else(即使只执行一个),并且表达式last - first对于非 RA 迭代器无效。
  • 即使已编译,我们也会在运行时(具有相关开销)针对我们可以在编译时立即测试的条件进行测试,并编译不需要的代码部分。(编译器可能能够优化它,但这就是概念。)

要修复它,您可以执行以下操作:

// (assume needed declarations...)

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distance(InIt first, InIt last)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::iterator_category Tag;

    // Choice
    return distanceImpl(first, last, is_same<Tag, random_access_iterator_tag>());
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, true_type)
{
    return last - first;
}

template<typename InIt>
typename iterator_traits<InIt>::difference_type
distanceImpl(InIt first, InIt last, false_type)
{
    // Make code shorter
    typedef typename iterator_traits<InIt>::difference_type Diff;

    Diff n = 0;
    while (first != last) {
        ++first;
        ++n;
    }
    return n;
}

或者(可能在这里)直接使用类型:

/* snip */
distance(InIt first, InIt last)
{
    /* snip */
    return distanceImpl(first, last, Tag());
}

/* snip */
distanceImpl(InIt first, InIt last, random_access_iterator_tag)
{
    return last - first;
}

/* snip */
distanceImpl(InIt first, InIt last, input_iterator_tag)
{
    /* snip */
    Diff n = 0;
    /* snip */
    return n;
}

现在只有“正确的”distanceImpl会被实例化和调用(选择在编译时完成)。

这是因为类型(例如InItor Tag)在编译时是已知的,并且is_same<Tag, random_access_iterator_tag>::value也是在编译时已知的常量。编译器只能基于类型(即重载解析)来解析要调用的重载。

注意:尽管“标签”是按值传递的,但它们仅用作“调度”的未命名、未使用的参数(不使用它们的值,仅使用它们的类型),编译器可以优化它们。

您还可以阅读第 47 条:使用特征类以获取有关Scott Meyers 的Effective C++, Third Edition中的类型的信息。

于 2013-06-30T10:48:42.527 回答
3

这种重载对于编译时优化很有用。请注意,在您所指的示例中, atypedef用于定义与or匹配的类型。此类型在编译时进行评估。在随后的函数调用中创建该类型的值只是为了调用该函数:您不能以类型作为参数来调用函数。std::true_typestd::false_type

您不能根据变量的执行重载。重载是根据类型执行的。

编码

bool match = my_list.contains(my_value);
coutResult(match);

不编译,因为没有coutResult(bool)功能:

error: no matching function for call to ‘coutResult(bool)’ 
note: candidates are: void coutResult(std::true_type) 
note:                 void coutResult(std::false_type)

因此,如果您的表达式可以在编译时进行评估,您可能会受益于重载函数true_typefalse_type从而在运行时删除额外的检查。但如果表达式不是常量,则必须使用if.

于 2013-06-30T10:35:29.253 回答
0

Reflection在 C++ 中,由于缺少属性,通常不能显式派生对象类型。如果要根据对象类型进行操作,就需要这样的函数重载。

如果要使用If语句,则需要为每个相关类定义一些额外的成员或方法以获取某些上下文可能不允许的类信息,这当然会引入额外的开销。作为一种更实用的方法,typeid可以使用这个额外的成员来代替,并且可以在运行时将对象的 id 与预期的类 id 进行比较。

此外,如果这些类彼此完全不同(比如没有共同的基础)并且它们是为完全独立的目的而定义和设计的,那么在单独的方法而不是单个条件中处理它们是一种很好的设计实践。

于 2013-06-30T09:08:55.157 回答