12

这可能是一个简单的问题,我根本不掌握 C++11 模板。

我有一个不是std::vector<T>出于性能原因的通用矢量类(非常具体的代码)。

我观察到检查是否T是 POD,如果是,则执行特殊计算比不检查效率高得多:

void vec<T>::clear() {
  if (!std::is_pod<T>::value) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
  }

  size = 0;
}

在这里,我不会T为每个项目调用析构函数(size可能真的很大),性能确实得到了提升。但是if (!std::is_pod<T>::value)一旦模板被编译,测试就没有用了:而不是被编译为:

void vec<int>::clear() {
  if (false) {
    for (int i = 0; i < size; i++) {
       data[i].~int();
    }
  }

  size = 0;
}

我希望它被编译为:

void vec<int>::clear() {
  size = 0;
}

编译器是否“聪明”足以跳过if (false)块或if (true)测试?我是否必须以不同的方式编写该代码?

4

3 回答 3

26

编译器是否“聪明”足以跳过 if (false) 块或 if (true) 测试?

当然是。死代码消除是例行执行的一项微不足道的优化。它的存在对于使许多调试库有效地工作也至关重要(= 在发布模式下没有运行时开销)。

但是我可能仍然会重写它,以使读者可以看到这是编译时的区别,方法是重载基于以下的函数is_pod

void vec<T>::do_clear(std::true_type) { }

void vec<T>::do_clear(std::false_type) {
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }
}

void vec<T>::clear() {
    do_clear(std::is_trivially_destructible<T>());
    size = 0;
}

在上面的代码中,我使用is_trivially_destructible而不是is_pod使代码更不言自明,正如 Nicol 在评论中所建议的那样。这种技术通常用于标准库实现和其他库中。它被称为标签调度

于 2013-06-19T09:45:39.493 回答
10

有一种称为伪析构函数的语言功能是专门为您想要做的事情而设计的。基本上给定一个类型模板参数 T 你可以在语法上为它调用一个析构函数,并且如果在实例化时 T 是一个标量类型(因为例如它是一个像 an 这样的基本类型int),它将编译并在它的地方。

对于非标量的其余 POD 类型,它们具有微不足道的析构函数,因此同样会生成空操作。

即使是最低优化设置的任何生产编译器都会在无操作上省略循环。所以你可以安全地写:

void vec<T>::clear() { 
    for (int i = 0; i < size; i++) {
       data[i].~T();
    }

    size = 0;
}

基本上,您正在尝试解决编译器已经为您处理的想象中的性能问题。

于 2013-06-19T12:18:57.013 回答
2

死代码消除是一种常见的优化。

但是,如果您根本不相信您的编译器会进行任何优化,您可以创建一个静态 iftemplate库。

如果您不想阅读一堆非常可怕的黑客,请跳到妙语。

#include <utility>
#include <type_traits>

template<bool b>
struct static_if_t {
  static_if_t( static_if_t const& ) = default;
  static_if_t() = default;
  static_if_t( static_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};

template<bool dead>
struct static_if_branch {};

template<bool b>
struct static_else_if_t {
  static_else_if_t( static_else_if_t const& ) = default;
  static_else_if_t() = default;
  static_else_if_t( static_else_if_t<b>(*)(std::integral_constant<bool,b>) ) {}
};

template<bool b>
static_if_t<b> static_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}
template<bool b>
static_else_if_t<b> static_else_if(std::integral_constant<bool,b> unused=std::integral_constant<bool,b>()) {return {};}

static auto static_else = static_else_if<true>;

template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<true> operator*( static_if_t<true>, Lambda&& closure )
{
  std::forward<Lambda>(closure)();
  return {};
}
template<typename Lambda, typename=typename std::enable_if< std::is_same< decltype(std::declval<Lambda&&>()()), decltype(std::declval<Lambda&&>()()) >::value >::type>
static_if_branch<false> operator*( static_if_t<false>, Lambda&& /*closure*/ )
{
  return {};
}

template<typename Unused>
static_if_branch<true> operator*( static_if_branch<true>, Unused&& ) {
  return {};
}

static_if_t< true > operator*( static_if_branch<false>, static_else_if_t<true> ) {
  return {};
}
static_if_t< false > operator*( static_if_branch<false>, static_else_if_t<false> ) {
  return {};
}

这是妙语:

#include <iostream>

int main() {
  static_if<true>* [&]{
    std::cout << "hello\n";
  } *static_else* [&]{
    std::cout << "doom\n";
  };

  static_if<false>* [&]{
    std::cout << "doom the\n";
  } *static_else* [&]{
    std::cout << "world\n";
  };

  static_if<false>* [&]{
    std::cout << "fello\n";
  } *static_else_if<false>* [&]{
    std::cout << "yellow\n";
  } *static_else_if<false>* [&]{
    std::cout << "hehe\n";
  };

  static_if( std::is_same<int, int>() )* [&]{
    std::cout << "int is int\n";
  };
  static_if( std::is_same<double, double>() )* [&]{
    std::cout << "double is double\n";
  } *static_else_if( std::is_same<int, double>() )* [&]{
    std::cout << "int is double\n";
  } *static_else* [&]{
    std::cout << "sky is not blue\n";
  };
}

但你为什么要这样做? 活生生的例子

(请注意,上面有两种语法static_if——一种static_if<compile time boolean expression>,另一种static_if( std::is_whatever<blah>() ))。

现在,虽然上面的内容完全是疯狂的,但上面的技术可以让你编写一个编译时三元运算符,它允许根据选择的分支选择不同的类型。这很整洁。

即,像这样:

auto result = trinary<std::is_same<A,B>::value>% 7 | 3.14;

并且类型result将是int如果AB是相同类型,并且double如果它们不同。甚至:

auto result = meta_trinary<std::is_same<A,B>::value>% [&]{return 7;} | [&]{return 3.14;};

如果您愿意,允许有条件地评估整个代码块,并存储返回值的条件类型。

于 2013-06-19T17:58:48.980 回答