Python Возвращаемые типы, переведенные на C ++ - PullRequest
1 голос
/ 04 августа 2020

Я унаследовал немного кода из Python для преобразования в C ++. Код Python вызывает исключение на основе кода ошибки, возвращаемого другими функциями. Код Python использует служебную функцию под названием errors(...), которая содержит словарь для сопоставления кодов ошибок с исключениями. Он возвращает тип исключения, а вызывающий код создает его собственное сообщение.

class BaseError(Exception):
    pass

class Error1(BaseError):
    pass

class Error2(BaseError):
    pass

def errors(code):
    errors_ = {
        1: BaseError,
        2: Error1,
        3: Error2
    }

    try:
        return errors_[code]
    except KeyError:
        return errors_[1]

def ReturnAnError():
    # Oops an error!
    return 2

try:
    val = ReturnAnError()
    if(val):
        raise errors(val)('{} Failed'.format("Example Failed"))
except BaseError as ex:
    print(ex)

Моя первоначальная мысль - выбрать простой путь и переопределить функцию Python errors(code) как void errors(uint8_t code, const char * msg) в C ++, а затем создайте оператор select, который сам вызовет исключение.

Есть ли более элегантное или краткое решение для прямого перевода этого кода? В частности, каков наиболее прямой перевод raise errors(val)('{} Failed'.format("Example Failed")) на c ++?

Ответы [ 2 ]

1 голос
/ 05 августа 2020

Одна функция с одинаковым именем и одинаковыми типами каждого параметра не может иметь два возвращаемых типа. Таким образом, нет возможности разрешить errors возвращать разные типы исключений. т.е. errors не может быть точно переведен.

Но для raise errors(val)("...") одним простым способом является throw исключение в errors. пример кода:

#include <string>
class BaseError {
public:
    std::string s;
    BaseError(const std::string &_s):s(_s){}
};
class Error1 : public BaseError {
public:
    Error1(const std::string &s):BaseError(s){}
};
class Error2 : public BaseError {
public:
    Error2(const std::string &s):BaseError(s){}
};

void errors(int val, const std::string &s)
{
    switch(val)
    {
    case 1:
        throw BaseError(s);
    case 2:
        throw Error1(s);
    case 3:
        throw Error2(s);
    default:
        throw BaseError(s);
    }
}

Или, если необходимо, object can throw в другом месте, один из способов - это создание исключений class can throw. пример кода:

class errors {
public:
    int val;
    std::string s;
    errors(int _val, const std::string _s): val(_val), s(_s){}
    void raise()
    {
        switch(val)
        {
        case 1:
            throw BaseError(s);
        case 2:
            throw Error1(s);
        case 3:
            throw Error2(s);
        default:
            throw BaseError(s);
        }
    }
};
1 голос
/ 04 августа 2020

Если я напрямую переведу ваш код на C ++:

#include <iostream>
#include <string>
#include <map>

class BaseError {
  public:
    BaseError(std::string s) : msg(s) {}
    virtual ~BaseError() {} // useless because inherited classes do not have attribute to delete
    friend std::ostream & operator<<(std::ostream & out, const BaseError & e) {
       out << e.msg;
       return out;
    }
    static BaseError * mk(std::string s) { return new BaseError(s); }
  private:
    std::string msg;
};

class Error1 : public BaseError {
  public:
    Error1(std::string s) : BaseError(s) {}
    static BaseError * mk(std::string s) { return new Error1(s); }
};

class Error2 : public Error1 {
  public:
    Error2(std::string s) : Error1(s) {}
    static BaseError * mk(std::string s) { return new Error2(s); }
};

typedef BaseError * (*fmk)(std::string);

fmk errors(int code) 
{
  const static std::map<int, fmk> error = {
       {1, &BaseError::mk},
       {2, &Error1::mk},
       {3, &Error2::mk}
  };
  std::map<int, fmk>::const_iterator it = error.find(code);
  
  return ((it == error.end()) ? error.find(1) : it)->second;
}

int ReturnAnError()
{
  // Oops an error!
  return 2;
}


int main()
{
  try {
    int val = ReturnAnError();
  
    if (val)
      throw (errors(val))("blah blah");
  }
  catch (BaseError * ex) {
      std::cout << *ex << std::endl;
      delete ex;
  }
}    

Компиляция и выполнение:

pi@raspberrypi:/tmp $ g++ c0.cc
pi@raspberrypi:/tmp $ ./a.out
blah blah
pi@raspberrypi:/tmp $ 

О функции ошибки :

  • Словарь Python может быть переведен на C ++ std::map

  • Насколько я знаю, в отличие от Python, я не могу указать в std::map адрес конструктора каждого класса, поэтому я использовал stati c операцию mk для каждого класса.

  • Чтобы избежать создавать std::map каждый раз, когда вызывается ошибок Я определил error staticconst, чтобы четко указать, что я не хочу его изменять), но это оптимизация и это не обязательно.

  • В Python исключения очень часто используются, это не так в C ++, поэтому я использую итератор, чтобы узнать, является ли код известный ключ, и я тестирую его.

Чтобы иметь возможность распечатать экземпляры классов, я перегрузил operator<<, в любом случае это не позволяет t o проверьте, что программа создала Error1 , и даже если я изменил код, чтобы он имел:

  try {
    int val = ReturnAnError();
  
    if (val)
      throw (errors(val))("blah blah");
  }
  catch (Error2 * ex) {
      std::cout << "err2" << *ex << std::endl;
      delete ex;
  }
  catch (Error1 * ex) {
      std::cout << "err1" << *ex << std::endl;
      delete ex;
  }
  catch (BaseError * ex) {
      std::cout << *ex << std::endl;
      delete ex;
  }

, выполненный код будет catch (BaseError * ex) {...}

Если я это сделаю:

  try {
    int val = ReturnAnError();
  
    if (val)
      throw *(errors(val))("blah blah");
  }
  catch (Error2 & ex) {
      std::cout << "err2" << ex << std::endl;
  }
  catch (Error1 & ex) {
      std::cout << "err1" << ex << std::endl;
  }
  catch (BaseError & ex) {
      std::cout << ex << std::endl;
  }

снова исполняемый код будет catch (BaseError & ex) {...} (и я создал утечку памяти).

Итак, операция virtual необходима, чтобы различать классы, например, при установке :

#include <iostream>
#include <string>
#include <map>

class BaseError {
  public:
    BaseError(std::string s) : msg(s) {}
    virtual ~BaseError() {} // useless because inherited classes do not have attribute to delete
    virtual void print(std::ostream & out) const { out << msg; }
    static BaseError * mk(std::string s) { return new BaseError(s); }
  private:
    std::string msg;
};

class Error1 : public BaseError {
  public:
    Error1(std::string s) : BaseError(s) {}
    virtual void print(std::ostream & out) const { 
      out << "error1 ";
      BaseError::print(out);
    }
    static BaseError * mk(std::string s) { return new Error1(s); }
};

class Error2 : public Error1 {
  public:
    Error2(std::string s) : Error1(s) {}
    virtual void print(std::ostream & out) const { 
      out << "error2 ";
      BaseError::print(out);
    }
    static BaseError * mk(std::string s) { return new Error2(s); }
};

typedef BaseError * (*fmk)(std::string);

fmk errors(int code) 
{
  const static std::map<int, fmk> error = {
       {1, &BaseError::mk},
       {2, &Error1::mk},
       {3, &Error2::mk}
  };
  std::map<int, fmk>::const_iterator it = error.find(code);
  
  return ((it == error.end()) ? error.find(1) : it)->second;
}

int ReturnAnError()
{
  // Oops an error!
  return 2;
}


int main()
{
  try {
    int val = ReturnAnError();
  
    if (val)
      throw (errors(val))("blah blah");
  }
  catch (BaseError * ex) {
      ex->print(std::cout);
      std::cout << std::endl;
      delete ex;
  }
}    

Компиляция и исполнение:

pi@raspberrypi:/tmp $ g++ c.cc
pi@raspberrypi:/tmp $ ./a.out
error1 blah blah
pi@raspberrypi:/tmp $ 

Есть ли более элегантное или лаконичное решение

Честно говоря, это как минимум не лаконично, но ваш код Python не слишком; -)

...