Как исправить поведение функции класса на основе параметров инициализации

#c #function #class #runtime

#c #функция #класс #время выполнения

Вопрос:

Я создаю класс C , который принимает определенные параметры во время инициализации и имеет некоторые функции, основанные на его частных переменных, что-то вроде compute функции здесь:

 class A {
  public:
    A(int x){
      a = x;
    }
    int compute(int y){
      if (a == 0){
        return y*y;
      }
      else if (a == 1){
        return 2*y;
      }
      else{
        return y;
      }
    }
  private:
    int a;
};

// usage

A myA(1); // private variables set only once
myA.compute(10); // this will check value of a 
myA.compute(1); // this will check value of a
 

Учитывая, что частные переменные устанавливаются во время инициализации и больше не будут изменены, есть ли какой-либо эффективный способ избежать проверки условий, связанных с частными переменными во время выполнения?

Приветствуется любая помощь. Спасибо

Ответ №1:

Вы можете создать шаблон функции compute() для int и использовать значение шаблона в качестве параметра. Вы можете увидеть результат на https://godbolt.org/z/14Mh4E

 class A {
public:
    A(int x) {
        a = x;
    }
    template <int y>
    constexpr int compute() const {
        if (a == 0) {
            return y * y;
        }
        else if (a == 1) {
            return 2 * y;
        }
        else {
            return y;
        }
    }
private:
    int a;
};

// usage

A myA(1); // private variables set only once
myA.compute<10>(); // this will check value of a 
myA.compute<1>(); // this will check value of a
 

Комментарии:

1. Это означает, что параметр y известен во время компиляции, что нежелательно или нет? Более того, поскольку член a не является constexpr , функция вычисления никогда не будет оцениваться как constexpr, и, следовательно, проверка условий будет выполняться всегда.

Ответ №2:

Вы могли бы избежать проверки условий, если бы использовали, например, объект функции в качестве члена и установили это условие на значение переменной a . В любом случае, я не думаю, что проверка условий будет большой проблемой с производительностью. Но это, конечно, будет зависеть от вашего приложения.

 #include <functional>
#include <iostream>

class A {
  public:
    A(int x)
    : a { x } 
    {
      if (a == 0){
        compute = [](int y){ return y*y; };
      }
      else if (a == 1){
        compute = [](int y){ return 2*y; };
      }
      else{
        compute = [](int y){ return y; };
      }

    }

    
    std::function<int(int)> compute;
    
  private:
    int a;
};

// usage


int main()
{
 
    A myA(1); // private variables set only once
    std::cout << myA.compute(10) << std::endl;
    std::cout << myA.compute(1) << std::endl;
    return 0;
}
 

Комментарии:

1. std::function было бы эквивалентно виртуальному вызову, не уверен, что это быстрее, чем включение a значения compute .

Ответ №3:

Вы можете гарантировать, что условия оцениваются во время компиляции с помощью constexpr . Обратите внимание, что в этом случае вы должны использовать C 14 для constexpr compute(...) , поскольку несколько операторов return поддерживаются в функциях constexpr только после C 14.

 #include <iostream>

class A {
  public:
    constexpr A(const int x): a(x) { }
    constexpr int compute(const int y) const {
      // Multiple return statements inside a constexpr function
      // requires C  14 or above.
      if (a == 0) {
        return y*y;
      }
      else if (a == 1) {
        return 2*y;
      }
      else {
        return y;
      }
    }
  private:
    int a;
};


int main() {
  constexpr A myA(1);
  constexpr int num = myA.compute(123);

  std::cout << num << std::endl;

  return EXIT_SUCCESS;
}
 

Эта страница содержит хорошее объяснение constexpr, а также примеры.

Комментарии:

1. Обратите внимание, что вам требуется, чтобы оба myA и y (123) были содержательным выражением, а также usage ( num ) .

Ответ №4:

Вы можете рассмотреть возможность использования шаблонного класса вместо закрытого члена:

 template< int I >
class A {
  public:
    int compute( int y ) {
      if constexpr ( I == 0 ) {
        return y * y;
      }
      else if constexpr ( I == 1 ) {
        return 2 * y;
      }
      else {
        return y;
      }
    }
};

// usage
A<1> myA;
myA.compute(10); // compile-time check
myA.compute(1); // compile-time check
 

Ответ №5:

Если параметры являются значением времени выполнения, я не вижу оптимального способа избежать условия или перехода.

Вы можете изменить свое условие с помощью виртуального вызова:

 struct A
{
    virtual ~A() = default;
    virtual int compute(int) = 0;
};

struct A0 { int compute(int y) override { return y * y; } };
struct A1 { int compute(int y) override { return 2 * y; } };
struct AN { int compute(int y) override { return y; } };

std::unique_ptr<A> makeA(int a)
{
    switch (a) {
        case 0: return std::make_unique<A0>();
        case 0: return std::make_unique<A1>();
        default: return std::make_unique<AN>();
    }
}
 

(компилятор может девиртуализировать вызов, если тип известен во время компиляции)

или «эквивалент»:

 struct A
{
    int (*f)(int); // or even std::function<int(int)> f; if you need capture.

    A(int a) : f(a == 0 ?  [](int y) { return y * y; }
               : a == 1 ?  [](int y) { return 2 * y; }
                        :  [](int y) { return y; })
    {}

    int compute(int y) { return f(y); }
};
 

(компилятору сложнее девиртуализировать стираемый тип)