11

#define外部ライブラリのヘッダーファイルにsのリストがあるとします。これら#defineは、関数から返されるエラーコードを表します。#defineエラーコードを入力として受け取り、実際の名前を表す文字列リテラルを出力として返すことができる変換関数を記述したいと思います。

例として、私が持っている場合

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2

関数を次のように呼び出せるようにしたい

int errorCode = doSomeLibraryFunction();
if (errorCode)
    writeToLog(convertToString(errorCode));

そして、convertToString()次のような巨大なスイッチケースになることなく、そのエラーコードを自動変換することができます

const char* convertToString(int errorCode)
{
    switch (errorCode)
    {
        case NO_ERROR:
           return "NO_ERROR";
        case ONE_KIND_OF_ERROR:
           return "ONE_KIND_OF_ERROR";
        ...
     ...
...

これが可能であれば、テンプレートとメタプログラミングを使用して可能になると思いますが、それはエラーコードが実際にはタイプであり、プロセッサマクロの束ではない場合にのみ機能します。

4

8 に答える 8

19

私は通常、巨大なスイッチケースの方法でそれを行いますが、次の方法でやや簡単にします:

#define STR(code) case code: return #code
switch (errorCode)
{
    STR(NO_ERROR);
    STR(ONE_KIND_OF_ERROR);
}

これは良い質問です。人々がどのようなより良い方法を持っているかを知りたいです

于 2010-04-16T04:48:33.960 に答える
6

生成されたコードで一般的な別の方法は次のとおりです。

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2
static const char* const error_names[] = {"NO_ERROR", "ONE_KIND_OF_ERROR", "ANOTHER_KIND_OF_ERROR"};

const char* convertToString(int errorCode) {return error_names[errorCode];}

私はすでに述べたスイッチケースの方法を好みますが、コードの構造によっては、ビルドプロセスの一部としてその配列を自動的に生成する方が簡単かもしれません

于 2010-04-16T04:56:58.247 に答える
4

あなたは正しいです。実行時にプリプロセッサで定義された識別子を回復する方法はありません (ソースを読むことができない限り、それは不正行為です)。名前の定数配列を作成し、エラー コードでインデックスを付けた方がよいでしょう (もちろん、適切な境界チェックを行います)。これを生成するスクリプトを作成するのは非常に簡単です。

于 2010-04-16T04:48:27.153 に答える
2

ブーストプリプロセッサを見てください。コード ペアのリスト/配列/シーケンスを作成できます。

#define codes ((1,"code1"))((...))

#define code1 1
...

// then use preprocessor FOR_EACH to generate error handlers

関連リンク:

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/seq_for_each.html

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/tuple_elem.html

于 2010-04-16T05:22:15.307 に答える
1

ここでの 1 つの可能性は、#defines を含む .h ファイルを解析し、convertToString() 関数の対応するソース コードを発行する小さなプログラムを作成することです。その後、.h ファイルが変更されるたびに、ビルド プロセスの一部としてそのプログラムを自動的に実行することができます。前もってもう少し作業が必要ですが、一度実装すると、int<->string 変換関数を手動で更新する必要がなくなります。

これは、同じ定数を使用する複数の言語のコードをサポートする場合に特に便利です。たとえば、私のアプリケーションの 1 つで、#defines ヘッダー ファイルを変更すると、対応する C++、Python、および Java ファイルが自動再生成されます。これにより、プロジェクトのメンテナンスがはるかに簡単になり、エラーが発生しにくくなります。

于 2010-04-16T05:23:29.343 に答える
1

を確実に保持したい場合は#define、マイケルのエレガントな#define STR(code)答えに行きます。しかし、define は C++ よりも C に近く、define の大きな欠点は、名前空間に定義できないことです。それらは、それらを含めるプログラムのグローバル名前空間を汚染します。変更する権限がある場合は、代わりに匿名の列挙型を使用することをお勧めします。

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

これはあなたが持っている s とまったく同じ#defineで、名前空間に入れることができます。そして今、static const char* const error_names配列を含むマイケルの他の回答を使用して、最初に要求したことを行うことができます。

于 2010-04-16T05:32:10.667 に答える
1

実際には両方の方法で使用できます。つまり、コードからエラーに変換する 2 つの関数を使用できます。

もちろん、最初のことは#define、定数には使用しないでください。おそらく列挙型が最適ですが、列挙型は拡張できません。これには、すべてのエラーを同じ場所で定義する必要があります (依存関係についてどうもありがとう. ..)

ただし、名前空間を使用してシンボルを分離し、前処理して世代全体を処理することで、別の方法で実行できます。ルックアップ部分では、Bimapを使用します。

最初に Handler クラスを定義する必要があります (すべてをインライン化する必要はありませんが、例としては簡単です)

#include <boost/bimap.hpp>
#include <boost/optional.hpp>

namespace error
{

  class Handler
  {
  public:
    typedef boost::optional<int> return_code;
    typedef boost::optional<std::string> return_description;

    static bool Register(int code, const char* description)
    {
      typedef error_map::value_type value_type;
      bool result = MMap().insert(value_type(code,description)).second;

      // assert(result && description)
      return result;
    }

    static return_code GetCode(std::string const& desc)
    {
      error_map::map_by<description>::const_iterator it
          = MMap().by<description>().find(desc);
      if (it != MMap().by<description>().end()) return it->second;
      else return return_code();
    }

    static return_description GetDescription(int c)
    {
      error_map::map_by<code>::const_iterator it
          = MMap().by<code>().find(c);
      if (it != MMap().by<code>().end()) return it->second;
      else return return_description();
    }

    typedef std::vector< std::pair<int,std::string> > errors_t;
    static errors_t GetAll()
    {
      errors_t result;
      std::for_each(MMap().left.begin(), MMap().left.end(),
                    result.push_back(boost::lambda::_1));
      return result;
    }

  private:
    struct code {};
    struct description {};

    typedef boost::bimap<
      boost::tagged<int, code>,
      boost::tagged<std::string, description>
    > error_map;

    static error_map& Map() { static error_map MMap; return MMap; }
  };

  // Short-Hand
  boost::optional<int> GetCode(std::string const& d)
  {
    return Handler::GetCode(d);
  }

  boost::optional<std::string> GetDescription(int c)
  { 
    return Handler::GetDescription(c);
  }
} // namespace error

次に、シンタックス シュガーを提供する必要があります。

#define DEFINE_NEW_ERROR(Code_, Description_)            \
  const int Description_ = Code_;                        \
  namespace error {                                      \
    const bool Description##_Registered =                \
      ::error::Handler::Register(Code_, #Description_);  \
  }

不明なエラー (たとえば assert) が登録された場合は、もう少し暴力的になる可能性があります。

そして、そのマクロを一度に複数のシンボルを定義できるマクロにいつでもラップできます... しかし、それは演習として残します。

使用法:

// someErrors.hpp
#include "error/handler.hpp"

DEFINE_NEW_ERROR(1, AnError)
DEFINE_NEW_ERROR(2, AnotherError)

// someFile.cpp
#include <iostream>
#include "error/handler.hpp"

int main(int argc, char* argv[])
{
  int code = 6;
  boost::optional<std::string> desc = error::GetDescription(code);

  if (desc)
  {
    std::cout << "Code " << code << " is mapped to <" << *desc << ">" << std::endl;
  }
  else
  {
    std::cout << "Code " << code << " is unknown, here is the list:\n";

    ::error::Handler::errors_t errors = ::Error::Handler::GetAll();

    std::for_each(errors.begin(), errors.end(), std::cout << "  " << _1);

    std::cout << std::endl;
  }
}

免責事項:ラムダ構文についてはよくわかりませんが、記述が簡素化されました。

于 2010-04-16T08:30:31.157 に答える
0

#define FOO 1単純なテキスト置換としてプリプロセッサによって処理されます。これらの定義を保持したい場合は、巨大なスイッチが必要です。

于 2010-04-16T04:52:58.197 に答える