#define、enum、またはconstを使用する必要がありますか?


125

私が取り組んでいるC ++プロジェクトでは、4つの値を持つことができるフラグの種類の値があります。これらの4つのフラグは組み合わせることができます。フラグはデータベースのレコードを表し、次のいずれかになります。

  • 新記録
  • 削除されたレコード
  • 変更されたレコード
  • 既存の記録

ここで、各レコードに対してこの属性を保持したいので、列挙型を使用できます。

enum { xNew, xDeleted, xModified, xExisting }

ただし、コードの他の場所では、ユーザーに表示するレコードを選択する必要があるため、次のように、それを単一のパラメーターとして渡すことができます。

showRecords(xNew | xDeleted);

だから、私は3つの可能なアプローチがあるようです:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

または

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

または

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

スペース要件は重要ですが(バイトと整数)、重要ではありません。定義を使用すると、型の安全性がenum失われ、一部のスペース(整数)が失われるため、ビット単位の操作を実行する場合はキャストする必要があります。でconst、ランダムuint8が誤って入る可能性があるので、タイプセーフティも失うと思います。

他にもっときれいな方法はありますか?

そうでない場合、何を使用しますか、またその理由は何ですか?

PS残りのコードは#defines なしのかなりクリーンな最新のC ++ であり、名前空間とテンプレートをいくつかのスペースで使用しているため、これらも問題ではありません。


「列挙型では、一部のスペース(整数)が失われます」。必ずしも。stackoverflow.com/questions/366017/…stackoverflow.com/questions/1113855/…(およびgccの-fshort-enumを参照してください。これらのCの回答はC ++でも当てはまると
思い

@pydave CとC ++の互換性について不明な点がある場合は、このリンクが非常に役立つと思います。たとえば、enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
これは投票数が多い古いトピックですが、この問題の状況でC ++ 11列挙型クラスについて言及しないのは理由があります。
Brandin 2014

注として、enum RecordType : uint8_tのタイプセーフenumとの小さいサイズを組み合わせuint8_tますが、ビットごとの演算子を提供する必要があります。
ジャスティン時間-

回答:


88

戦略を組み合わせて、単一のアプローチの欠点を減らします。私は組み込みシステムで作業しているため、次の解決策は、整数およびビット単位の演算子が高速で、メモリが少なく、フラッシュの使用量が少ないという事実に基づいています。

列挙型を名前空間に配置して、定数がグローバル名前空間を汚染しないようにします。

namespace RecordType {

列挙型は、型指定されたチェック時のコンパイル時間を宣言および定義します。常にコンパイル時の型チェックを使用して、引数と変数に正しい型が指定されていることを確認してください。C ++ではtypedefは必要ありません。

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

無効な状態の別のメンバーを作成します。これはエラーコードとして役立ちます。たとえば、状態を返したいがI / O操作が失敗した場合などです。また、デバッグにも役立ちます。初期化リストとデストラクタで使用して、変数の値を使用する必要があるかどうかを確認します。

xInvalid = 16 };

このタイプには2つの目的があると考えてください。レコードの現在の状態を追跡し、特定の状態のレコードを選択するためのマスクを作成します。タイプの値が目的に対して有効かどうかをテストするインライン関数を作成します。状態マーカー対状態マスクとして。これは、初期化されていない変数や誤って指定された変数を介して変数に存在する可能性のある値や値なのでtypedef、バグをキャッチします。int0xDEADBEEF

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

usingタイプを頻繁に使用する場合は、ディレクティブを追加します。

using RecordType ::TRecordType ;

値チェック関数は、アサートで使用された直後に不正な値をトラップするのに役立ちます。実行時にバグをすばやく検出するほど、発生する可能性のある損傷が少なくなります。

以下に、すべてをまとめた例をいくつか示します。

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

正しい値の安全性を確保する唯一の方法は、演算子のオーバーロードを伴う専用クラスを使用することであり、それは別の読者のための演習として残されています。


1
たいていは良い答えですが、質問ではフラグを組み合わせることができ、IsValidState()関数ではフラグを組み合わせることはできません。
ジョナサンレフラー

3
@ジョナサン・レフラー:私が立っているところから、 'IsValidState'はそうするべきではないと思います、 'IsValidMask'はそうです。
ジョアン・ポルテラ

1
IsValidMask何も選択できない(つまり0)ことが望ましいですか?
Joachim Sauer

2
−1実行時の型チェックの考え方は嫌なものです。
乾杯とhth。-Alf

54

定義を忘れる

彼らはあなたのコードを汚染します。

ビットフィールド?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

これを使用しないでください。4 intを節約するよりも速度に関心があります。ビットフィールドの使用は、他のタイプへのアクセスよりも実際には低速です。

ただし、構造体のビットメンバーには実用上の欠点があります。まず、メモリ内のビットの順序はコンパイラごとに異なります。さらに、多くの一般的なコンパイラは、ビットメンバーの読み取りと書き込みのための非効率的なコードを生成します、ほとんどのマシンはメモリ内の任意のビットセットを操作できないため、ビットフィールド(特にマルチプロセッサシステム)に関連する潜在的な重大なスレッドセーフティの問題があります。代わりに、単語全体を読み込んで保存する必要があります。たとえば、以下はミューテックスを使用しているにもかかわらず、スレッドセーフではありません

ソース: http //en.wikipedia.org/wiki/Bit_field

そしてあなたがするより多くの理由が必要な場合 ビットフィールドを使用ないレイモンドチェンThe Old New Thing Post:ブール値のコレクションのビットフィールドの費用便益分析で説得する可能性があります。http://blogs.msdn.com/oldnewthing/アーカイブ/2008/11/26/9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

それらを名前空間に置くのはクールです。それらがCPPまたはヘッダーファイルで宣言されている場合、それらの値はインライン化されます。これらの値でスイッチを使用できますが、カップリングが少し増加します。

ああ、はい:staticキーワードを削除します。staticは、使用時にC ++で非推奨になり、uint8が組み込みタイプの場合、同じモジュールの複数のソースに含まれるヘッダーでこれを宣言するためにこれを必要としません。最後に、コードは次のようになります。

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

このアプローチの問題は、コードが定数の値を認識しているため、結合がわずかに増加することです。

列挙型

const intと同じですが、タイピングがやや強力です。

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

彼らはまだグローバル名前空間を汚染しています。ところで... typedefを削除します。C ++で作業しています。列挙型と構造体のこれらのtypedefは、何よりもコードを汚染します。

結果はちょっとです:

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

ご覧のように、列挙型はグローバル名前空間を汚染しています。この列挙型を名前空間に入れると、次のようになります。

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

カップリングを減らしたい場合(つまり、定数の値を非表示にできるため、完全な再コンパイルを必要とせずに、必要に応じてそれらを変更できます)、intをヘッダーでexternとして、CPPファイルで定数として宣言できます。 、次の例のように:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

そして:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

ただし、これらの定数でスイッチを使用することはできません。だから最後に、あなたの毒を選びます... :-p


5
なぜビットフィールドは遅いと思いますか?実際にそれと別の方法を使用してコードのプロファイルを作成しましたか?たとえそうであっても、スピードよりも明確さの方が重要な場合があり、「これを使用しない」ことを少し単純化します。
wnoise 2008

「静的const uint8 xNew;」C ++ではconst名前空間スコープの変数はデフォルトで内部リンケージになるため、冗長です。「const」を削除すると、外部リンクが含まれます。また、「enum {...} RecordType;」型が匿名の列挙型である「RecordType」という名前のグローバル変数を宣言します。
bk1e 2008

onebyone:最初に、主な理由は、損失(ある場合は数バイト)が損失(アクセスが遅い、読み取りと書き込みの両方)によって追い越されたということでした...
paercebal

3
onebyone:次に、私が職場または自宅で生成するすべてのコードは、本質的にスレッドセーフです。簡単です。ロック保護されていない限り、グローバル、静的、スレッド間での共有はありません。このイディオムを使用すると、この基本的なスレッドセーフ性が損なわれます。そして何のために?おそらく数バイト?... :-) ...
paercebal

ビットフィールドの隠れたコストに関するレイモンドチェンの記事への参照を追加しました。
paercebal 2008年

30

std :: bitsetを除外しましたか?フラグのセットはそのためのものです。行う

typedef std::bitset<4> RecordType;

その後

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

ビットセットには演算子のオーバーロードがたくさんあるため、次のことができます

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

または、それに非常に似たもの-私はこれをテストしていないので、修正があればありがたいです。インデックスでビットを参照することもできますが、一般的には定数のセットを1つだけ定義するのが最善です。おそらくRecordType定数の方が便利です。

あなたがビットセットを除外していると仮定して、私は列挙型に投票しますます。

列挙型をキャストすることは重大な欠点であることを私は購入しません-わかりましたので少しうるさく、列挙型に範囲外の値を割り当てることは未定義の動作ですので、いくつかの異常なC ++で理論的に自分自身を撃つことが可能です実装。しかし、必要なときにだけ(intからenum iircに移行するときに)実行する場合、それは、以前に見られたまったく正常なコードです。

列挙型のスペースコストについても疑わしいです。uint8変数とパラメーターはおそらくintよりも少ないスタックを使用しないため、クラス内のストレージのみが重要です。構造体に複数のバイトをパックすると成功する場合があります(その場合、列挙型をuint8ストレージの内外にキャストできます)。

したがって、列挙型には他の列挙型と比べて不利な点はありません。利点として、型の安全性(明示的にキャストしないとランダムな整数値を割り当てることはできません)と、すべてを参照するクリーンな方法が得られます。

ちなみに、列挙型には "= 2"も指定します。必須ではありませんが、「驚きが最も少ない原理」は、4つの定義すべてが同じように見えることを示唆しています。


1
実際、私はビットセットをまったく考慮していませんでした。しかし、それが良いかどうかはわかりません。ビットセットでは、ビットを1、2、3、4としてアドレス指定する必要があります。これにより、コードが読みにくくなります。つまり、ビットに「名前を付ける」ために列挙型を使用することになります。スペースセーバーかもしれません。ありがとう。
ミラノバブスコフ

ミラノ、列挙型を使用してビットに「名前を付ける」必要はありません。上記のように定義済みのビットを使用できます。my_bitset.flip(1)ではなくビット1をオンにする場合は、my_bitset | = xNew;を実行します。
モスワルド2008

これはあなたに向けられることは少なく、STLに向けられますが、私は本当に尋ねる必要がありますbitset。それは通常long、いずれにせよ(私の実装ではiirc、はい、どれほど無駄が多い)または同様の各要素の整数型に変換されるので、難読化されていない積分を使用しないのはなぜですか?(または、現在、constexprストレージがゼロの場合)
underscore_d

[編集タイムアウト] ...しかし、私はbitsetクラスの理論的根拠を本当に理解したことがありません。 」
underscore_d

" uint8変数とパラメーターはおそらくints" よりも少ないスタックを使用しません"は間違っています。8ビットのレジスタを備えたCPUがある場合、int場合、少なくとも2つのレジスタがuint8_t必要で、1つしか必要ないため、レジスタが不足する可能性が高いため、より多くのスタックスペースが必要になります(これも遅くなり、コードサイズが大きくなる可能性があります(命令セットに応じて))。(あなたはタイプを持っています、それはすべきではありuint8_tませんuint8
12431234123412341234123 '25


5

可能であれば、マクロを使用しないでください。最新のC ++に関しては、それほど高く評価されていません。


4
そうだね。私がマクロについて私が嫌いなのは、マクロが間違っていると、マクロにステップインできないことです。
カール、

それはコンパイラーで修正できるものだと思います。
celticminstrel 2015

4

列挙型は、「識別子の意味」とタイプセーフを提供するため、より適切です。「xDeleted」が「RecordType」であり、「レコードのタイプ」を表す(すごい!)ことは、何年経ってもはっきりわかります。Constsはそのためのコメントを必要とし、コード内を上下する必要もあります。


4

定義するとタイプセーフが失われます

必ずしも...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

列挙型を使用すると、一部のスペースが失われます(整数)

必ずしもそうとは限りませんが、ストレージのポイントで明示する必要があります...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

おそらく、ビット単位の操作を実行する場合はキャストする必要があります。

オペレーターを作成して、その苦痛を取り除くことができます。

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

constを使用すると、ランダムなuint8が誤って入る可能性があるため、タイプセーフも失うと思います。

同じことがこれらのメカニズムのいずれでも発生する可能性があります。範囲と値のチェックは通常、タイプセーフと直交しています(ただし、ユーザー定義型(つまり、独自のクラス)は、データについて「不変条件」を強制できます)。列挙型を使用すると、コンパイラは値をホストするためにより大きい型を自由に選択でき、初期化されていない、破損した、または単に設定されていない列挙型変数は、ビットパターンを予期しない数として解釈する可能性があります。列挙識別子、それらの任意の組み合わせ、および0。

他にもっときれいな方法はありますか?/そうでない場合、何を使用しますか、またその理由は何ですか?

結局のところ、画像にビットフィールドとカスタム演算子があると、列挙型の信頼できるCスタイルのビット単位ORはかなりうまく機能します。mat_geekの回答のように、いくつかのカスタム検証関数とアサーションを使用して、堅牢性をさらに向上させることができます。多くの場合、文字列、int、double値などの処理に等しく適用できる手法。

これは「よりクリーン」であると主張できます。

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

私は無関心です:データビットはより密に詰め込まれますが、コードは大幅に増加します...取得したオブジェクトの数に依存し、lambbas-それらは美しい-はまだ厄介であり、ビット単位のORよりも正しく計算することは困難です。

ところで/-スレッドセーフティのかなり弱いIMHOに関する議論-支配的な決定を推進する力になるのではなく、背景の検討事項として覚えておくのが一番です。ビットフィールド間でミューテックスを共有することは、それらのパッキングに気づいていない場合でも、より可能性の高い方法です(ミューテックスは比較的かさばるデータメンバーです-1つのオブジェクトのメンバーに複数のミューテックスを含めることを検討する場合は、パフォーマンスについて本当に心配する必要があります。それらがビットフィールドであることに気付くのに十分です)。サブワードサイズのタイプでも同じ問題が発生する可能性があります(例:)uint8_t。とにかく、より高い同時実行性が必要な場合は、アトミックな比較とスワップスタイルの操作を試すことができます。


1
+1罰金。ただし、operator|unsigned int命令の前に整数型()にキャストする必要があります|。そうしないoperator|と、再帰的に自分自身を呼び出し、ランタイムスタックオーバーフローを引き起こします。私は提案します:return RecordType( unsigned(lhs) | unsigned(rhs) );。乾杯
olibre

3

列挙型を格納するために4バイトを使用する必要がある場合でも(私はC ++に精通していません-基礎となる型をC#で指定できることを知っています)、それでも価値があります-列挙型を使用します。

GBのメモリを搭載したサーバーの今日の時代では、一般に、アプリケーションレベルでの4バイトと1バイトのメモリのようなことは問題ではありません。もちろん、特定の状況でメモリ使用量がそれほど重要でない場合(そしてC ++でバイトを使用して列挙型をサポートできない場合)、「静的const」ルートを検討できます。

結局のところ、自問する必要があります。データ構造の3バイトのメモリ節約に「静的const」を使用することは、メンテナンスヒットに値するのでしょうか。

他に覚えておくべきこと-IIRC、x86では、データ構造は4バイト境界で整列されているため、「レコード」構造に多数のバイト幅要素がない限り、実際には問題にならない可能性があります。パフォーマンスとスペースの保守性のトレードオフを行う前に、テストして確認してください。


言語リビジョンC ++ 11以降、C ++で基になる型を指定できます。それまでは、「少なくとも、指定されたすべての列挙子のビットフィールドとして格納して使用するのに十分な大きさでしたが、おそらくintそれが小さすぎない限り」です。[C ++ 11で基になる型を指定しない場合は、従来の動作が使用されます。逆に、C ++ 11 enum classの基になる型intは、特に指定されていない場合、明示的にデフォルトに設定されます。]
Justin Time-Monica

3

列挙型構文とビットチェックの便利さを備えたクラスのタイプセーフが必要な場合は、C ++のセーフラベルを検討してください。ください。私はその作者と一緒に仕事をしてきましたが、彼はとても賢いです。

ただし、注意してください。結局、このパッケージはテンプレートマクロを使用ます!


私の小さなアプリにとってはやり過ぎのようです。しかし、それは良い解決策のようです。
ミラノバブスコフ2008

2

概念的に全体としてフラグ値を渡す必要がありますか、それとも多数のフラグごとのコードが必要ですか?いずれにせよ、1ビットのビットフィールドのクラスまたは構造体としてこれを持っていると、実際にはより明確になると思います。

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

次に、レコードクラスはstruct RecordFlagメンバー変数を持つことができ、関数はstruct RecordFlagタイプの引数を取ることができます。コンパイラーはビットフィールドをまとめてパックし、スペースを節約する必要があります。


時には全体として、時にはフラグとして。また、特定のフラグが設定されているかどうかをテストする必要もあります(全体として渡す場合)。
ミラノバブスコフ

まあ、別々の場合は、intを要求してください。一緒になったら、構造体を渡します。
wnoise 2008

それは良くなりません。ビットフィールドへのアクセスは、何よりも遅いです。
paercebal 2008

本当に?コンパイラーは、ビットフィールドをテストするために手動のビットいじりとは大きく異なるコードを生成すると思いますか?そして、それは大幅に遅くなりますか?どうして?慣用的にこれを簡単に実行できないのは、一度に複数のフラグをマスクすることです。
wnoise 2008

単純な読み取りテストを実行すると、ビットマスキングに5.50〜5.58秒、ビットフィールドアクセスに5.45〜5.59秒かかります。ほとんど区別がつかない。
wnoise 2008

2

値を組み合わせることができるこの種のことには、おそらく列挙型を使用しないでしょう。より一般的には、列挙型は相互に排他的な状態です。

ただし、どちらの方法を使用する場合でも、これらが結合できるビットの値であることをより明確にするために、代わりに実際の値に次の構文を使用します。

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

そこで左シフトを使用すると、各値が1ビットであることを示すのに役立ち、後で誰かが新しい値を追加して何かに値9を割り当てるなど、何か間違ったことをする可能性は低くなります。


1
特にioctl()の定数には、そのための十分な前例があります。ただし、16進定数を使用したいのですが、0x01、0x02、0x04、0x08、0x10、...
Jonathan Leffler

2

KISS高い凝集性、低い結合に基づいて、次の質問をしてください-

  • 誰が知る必要がありますか?私のクラス、私のライブラリ、他のクラス、他のライブラリ、サードパーティ
  • どのレベルの抽象化を提供する必要がありますか?消費者はビット操作を理解していますか。
  • VB / C#などからインターフェイスする必要がありますか?

優れた本「Large-Scale C ++ Software Design」があり、これは、別のヘッダーファイル/インターフェイスの依存性を回避する必要がある場合に、基本型を外部的に促進します。


1
a)5-6クラス。b)私だけ、それは1人のプロジェクトですc)インターフェースなし
MilanBabuškov'22 / 09/22


0

私はむしろ行く

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

単純に〜だから:

  1. それはよりクリーンであり、コードを読みやすく維持しやすくします。
  2. 定数を論理的にグループ化します。
  3. あなたの仕事それらの3バイトを保存することでない限り、プログラマーの時間はより重要です。

まあ、私はクラスRecordの100万のインスタンスを簡単に持つことができるので、それは重要かもしれません。OTOH、それは1MBと4MBの違いに過ぎないので、心配する必要はないでしょう。
ミラノバブシュコフ2008

@Vivek:整数幅の制限を考慮しましたか?特にC ++ 11以前。
user2672165 2013

0

すべてをオーバーエンジニアリングするのは好きではありませんが、これらのケースでは、この情報をカプセル化する(小さな)クラスを作成する価値がある場合があります。クラスRecordTypeを作成すると、次のような関数が含まれる可能性があります。

void setDeleted();

void clearDeleted();

bool isDeleted();

等...(または慣習に合うもの)

組み合わせを検証できます(すべての組み合わせが有効ではない場合、たとえば、「新規」と「削除」の両方を同時に設定できない場合)。ビットマスクなどを使用しただけの場合、状態を設定するコードは検証する必要があります。クラスはそのロジックもカプセル化できます。

このクラスは、各状態に意味のあるログ情報を添付する機能も提供します。現在の状態などの文字列表現を返す関数を追加できます(またはストリーミングオペレーター '<<'を使用します)。

ストレージについて心配している場合でも、クラスに 'char'データメンバーのみを含めることができるので、少量のストレージのみを使用してください(仮想ではないと想定)。もちろん、ハードウェアなどによっては、位置合わせの問題があるかもしれません。

ヘッダーファイルではなくcppファイル内の匿名の名前空間にある場合、実際のビット値が「世界」の残りの部分に表示されない可能性があります。

enum /#define /ビットマスクなどを使用するコードに無効な組み合わせやロギングなどを処理するための多くの「サポート」コードがあることがわかった場合は、クラスでのカプセル化を検討する価値があります。もちろん、ほとんどの場合、単純な問題は単純な解決策でよりうまくいきます...


残念ながら、宣言は.hファイルで行う必要があります。プロジェクト全体で使用されるためです(一部の5〜6個のクラスで使用されます)。
ミラノバブスコフ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.