8

我正在测试各种优化的组合,为此我需要一个静态如果,如http://channel9.msdn.com/Events/GoingNative/GoingNative-2012/Static-If-I-Had-a-Hammer中所述启用并禁用特定优化。if(const-expr) 并不总是有效,因为某些优化涉及更改数据布局,而这不能在函数范围内完成。

基本上我想要的是这样的:

template<bool enable_optimization>
class Algo{
  struct Foo{
    int a;
    if(enable_optimization){
      int b;
    }

    void bar(){
      if(enable_optimization){
        b = 0;
      }
    }
  };
};

(是的,在我的情况下,从数据布局中删除 b 的较小内存占用是相关的。)

目前我正在使用非常糟糕的黑客来伪造它。我正在寻找一种更好的方法来做到这一点。

档案啊

#ifndef A_H
#define A_H
template<class enable_optimization>
class Algo;
#include "b.h"
#endif

文件 bh(此文件由 Python 脚本自动生成)

#define ENABLE_OPTIMIZATION 0
#include "c.h"
#undef
#define ENABLE_OPTIMIZATION 1
#include "c.h"
#undef

文件通道

template<>
class Algo<ENABLE_OPTIMIZATION>{
  struct Foo{
    int a;
    #if ENABLE_OPTIMIZATION
    int b;
    #endif

    void bar(){
      #if ENABLE_OPTIMIZATION
      b = 0;
      #endif
    }
  };
};

有谁知道这样做的更好方法?理论上它可以使用模板元编程来完成,起初我使用它。至少我使用它的方式很痛苦,并导致完全不可读和臃肿的代码。使用上面的 hack 可以显着提高生产力。

编辑:我有几个优化标志,它们相互作用。

4

3 回答 3

6

没有理由使用模板使代码变得更加复杂:

template<bool enable_optimization>
  class FooOptimized
  {
  protected:
    int b;
    void bar_optim()
    {
      b = 0;
    }
  };

template<>
  class FooOptimized<false>
  {
  protected:
    void bar_optim() { }
  };

template<bool enable_optimization>
  struct Algo
  {
    struct Foo : FooOptimized<enable_optimization>
    {
      int a;

      void bar()
      {
        this->bar_optim();
      }
    };
  };

不需要元编程,只需将根据是否启用优化而变化的部分分离为新类型并对其进行专门化。

因为新类型在为空(即没有成员)时用作基类FooOptimized::b,所以不会占用空间,所以sizeof(Algo<false>::Foo) == sizeof(int).


(请随意忽略此答案的其余部分,它没有直接解决问题,而是提出了一种具有不同权衡的不同方法。它是否“更好”完全取决于真实代码的细节,在问题中给出的简单示例中未显示。)

作为一个相关但独立的问题,不依赖于是否启用优化的部分和仍然Algo依赖于模板参数,因此尽管您只编写这些代码位一次,编译器将生成两组目标代码. 根据该代码中的工作量及其使用方式,您可能会发现将其更改为非模板代码有一个优势,即用动态多态性替换静态多态性。例如,您可以使标志成为运行时构造函数参数而不是模板参数:Algo::Fooenable_optimization

struct FooImpl
{
  virtual void bar() { }
};

class FooOptimized : FooImpl
{
  int b;
  void bar()
  {
    b = 0;
  }
};

struct Algo
{
  class Foo
  {
    std::unique_ptr<FooImpl> impl;
  public:
    explicit
    Foo(bool optimize)
    : impl(optimize ? new FooOptimized : new FooImpl)
    { }

    int a;

    void bar()
    {
      impl->bar();
    }
  };
};

您必须对其进行分析和测试,以确定虚拟功能是否比代码重复具有更多或更少的开销,Algo并且Algo::Foo不依赖于模板参数。

于 2012-07-21T14:12:02.613 回答
1

注意:就目前而言,这种方法不起作用,因为如果不为该成员分配空间,似乎无法在一个类中拥有一个成员。如果您知道如何使其工作,请随时进行编辑。

你可以使用这样的成语:

template<bool optimized, typename T> struct dbg_obj {
  struct type {
    // dummy operations so your code will compile using this type instead of T
    template<typename U> type& operator=(const U&) { return *this; }
    operator T&() { return *static_cast<T*>(0); /* this should never be executed! */ }
  };
};
template<typename T> struct dbg_obj<false, T> {
  typedef T type;
};

template<bool enable_optimization>
class Algo{
  struct Foo{
    int a;
    typename dbg_obj<enable_optimization, int>::type b;

    void bar(){
      if(enable_optimization){
        b = 0;
      }
    }
  };
};

如果优化被禁用,这会给你一个普通的int成员。如果启用优化,则类型为b不带成员的结构,不会占用任何空间。

由于您的方法bar使用看起来像运行时的​​东西if来决定是否访问b,而不是像模板专业化这样的明确的编译时机制,因此您使用的所有操作也b必须可以从虚拟结构中获得。即使相关部分永远不会被执行,并且编译器很可能将它们优化掉,正确性检查仍然是第一位的。因此,该行b = 0也必须针对替换类型进行编译。这就是虚拟分配和虚拟演员操作的原因。尽管其中任何一个都足以满足您的代码,但我将两者都包括在内,以防它们在其他时候证明有用,并让您了解如何在需要它们时添加更多内容。

于 2012-07-21T15:02:04.030 回答
0

Jonathan Wakely 提出的静态解决方案(不是动态解决方案)是可行的方法。

这个想法很简单:

  1. 将“特定”数据隔离到自己的类中(相应地进行模板化和专门化)
  2. 对该数据的所有访问都完全交给专用类(接口必须统一)

然后,为了不产生空间开销,您可以通过继承这个特殊类的任一特化来使用 EBO(空基优化)。

注意:一个属性至少需要占用 1 个字节的空间,基类在某些情况下是不需要的(例如为空)。

在你的情况下:

  • b是具体数据
  • 有一个涉及它的操作(包括设置它的值)

所以我们可以很容易地构造一个类:

template <typename T> struct FooDependent;

template <>
struct FooDependent<true> {
    int b;
    void set(int x) { b = x; }
};

template <>
struct FooDependent<false> {
    void set(int) {}
};

然后我们可以将它注入到 中Foo,使用 EBO 是我们的优势:

struct Foo: FooDependent<enable_optimization> {
    int a;

    void bar() { this->set(0); }
};

注意:this在模板代码中使用来访问基类的成员,否则好的编译器会拒绝你的代码。

于 2012-07-21T16:13:33.970 回答