Cプリプロセッサは、C++コミュニティによって正当に恐れられ、避けられています。通常、インライン関数、const、およびテンプレートは、#define
よりも安全で優れた代替手段です。
次のマクロ:
#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0)
タイプセーフよりも優れているわけではありません:
inline bool succeeded(int hr) { return hr >= 0; }
ただし、マクロには場所があります。プリプロセッサなしでは実行できないできないマクロの用途をリストしてください。
各ユースケースを別々の回答に入れて投票できるようにしてください。また、前処理者なしで回答の1つを達成する方法を知っている場合は、その回答のコメントでその方法を指摘してください。
__FILE__
、__LINE__
などのようなものを自動的に渡すデバッグ関数のラッパーとして:
#ifdef ( DEBUG )
#define M_DebugLog( msg ) std::cout << __FILE__ << ":" << __LINE__ << ": " << msg
#else
#define M_DebugLog( msg )
#endif
メソッドは常に完全でコンパイル可能なコードでなければなりません。マクロはコードフラグメントである場合があります。したがって、foreachマクロを定義できます。
#define foreach(list, index) for(index = 0; index < list.size(); index++)
そして、次のように使用します:
foreach(cookies, i)
printf("Cookie: %s", cookies[i]);
C++ 11以降、これは 範囲ベースのforループ に置き換えられました。
ヘッダーファイルガードにはマクロが必要です。
必要マクロの他の領域はありますか?多くはありません(もしあれば)。
マクロの恩恵を受ける他の状況はありますか?はい!!!
マクロを使用する場所の1つは、非常に反復的なコードを使用することです。たとえば、他のインターフェイス(.NET、COM、Pythonなど)で使用するC++コードをラップする場合、さまざまな種類の例外をキャッチする必要があります。以下がその方法です。
#define HANDLE_EXCEPTIONS \
catch (::mylib::exception& e) { \
throw gcnew MyDotNetLib::Exception(e); \
} \
catch (::std::exception& e) { \
throw gcnew MyDotNetLib::Exception(e, __LINE__, __FILE__); \
} \
catch (...) { \
throw gcnew MyDotNetLib::UnknownException(__LINE__, __FILE__); \
}
これらのキャッチをすべてのラッパー関数に配置する必要があります。毎回完全なcatchブロックを入力するのではなく、次のように入力します。
void Foo()
{
try {
::mylib::Foo()
}
HANDLE_EXCEPTIONS
}
これにより、メンテナンスも容易になります。新しい例外タイプを追加する必要がある場合、追加する必要がある場所は1つだけです。
他にも便利な例があります。その多くには、__FILE__
および__LINE__
プリプロセッサマクロが含まれています。
とにかく、マクロは正しく使用すると非常に便利です。マクロは悪ではありません-それらの誤用は悪です。
主に:
__LINE__
や__FILE__
などの定義済みマクロ)コンパイラ間の違いの問題を克服するための条件付きコンパイルの内部:
#ifdef ARE_WE_ON_WIN32
#define close(parm1) _close (parm1)
#define rmdir(parm1) _rmdir (parm1)
#define mkdir(parm1, parm2) _mkdir (parm1)
#define access(parm1, parm2) _access(parm1, parm2)
#define create(parm1, parm2) _creat (parm1, parm2)
#define unlink(parm1) _unlink(parm1)
#endif
式から文字列を作成する場合、これの最良の例はassert
です(#x
はx
の値を文字列に変換します)。
#define ASSERT_THROW(condition) \
if (!(condition)) \
throw std::exception(#condition " is false");
const char *
よりも文字列リテラルでより多くのことができるため、文字列定数はマクロとしてより適切に定義されることがあります。
例えば文字列リテラルは 簡単に連結 にできます。
#define BASE_HKEY "Software\\Microsoft\\Internet Explorer\\"
// Now we can concat with other literals
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "Settings", &settings);
RegOpenKey(HKEY_CURRENT_USER, BASE_HKEY "TypedURLs", &URLs);
const char *
が使用された場合、実行時に連結を実行するには、何らかの文字列クラスを使用する必要があります。
const char* BaseHkey = "Software\\Microsoft\\Internet Explorer\\";
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "Settings").c_str(), &settings);
RegOpenKey(HKEY_CURRENT_USER, (string(BaseHkey) + "TypedURLs").c_str(), &URLs);
関数のプログラムフロー(return
、break
、およびcontinue
)を変更する場合、関数内で実際にインライン化されるコードとは異なる動作をします。
#define ASSERT_RETURN(condition, ret_val) \
if (!(condition)) { \
assert(false && #condition); \
return ret_val; }
// should really be in a do { } while(false) but that's another discussion.
明らかなインクルードガード
#ifndef MYHEADER_H
#define MYHEADER_H
...
#endif
通常の関数呼び出しを使用して、関数呼び出し引数の短絡を実行することはできません。例えば:
#define andm(a, b) (a) && (b)
bool andf(bool a, bool b) { return a && b; }
andm(x, y) // short circuits the operator so if x is false, y would not be evaluated
andf(x, y) // y will always be evaluated
nitTest ++ のようなC++のユニットテストフレームワークは、プリプロセッサマクロを中心に展開します。ユニットテストコードの数行は、手動で入力するのはまったく面白くないクラスの階層に展開されます。 UnitTest ++のようなものがなく、プリプロセッサの魔法がなければ、C++の単体テストを効率的に記述する方法がわかりません。
ヘッダーガードのような明らかなものを無視するとしましょう。
場合によっては、プリコンパイラによってコピー/貼り付けが必要なコードを生成することがあります。
#define RAISE_ERROR_STL(p_strMessage) \
do \
{ \
try \
{ \
std::tstringstream strBuffer ; \
strBuffer << p_strMessage ; \
strMessage = strBuffer.str() ; \
raiseSomeAlert(__FILE__, __FUNCSIG__, __LINE__, strBuffer.str().c_str()) \
} \
catch(...){} \
{ \
} \
} \
while(false)
これにより、これをコーディングできます。
RAISE_ERROR_STL("Hello... The following values " << i << " and " << j << " are wrong") ;
次のようなメッセージを生成できます。
Error Raised:
====================================
File : MyFile.cpp, line 225
Function : MyFunction(int, double)
Message : "Hello... The following values 23 and 12 are wrong"
テンプレートとマクロを混在させると、さらに良い結果が得られることに注意してください(つまり、変数名と値を並べて自動的に生成する)
また、デバッグ情報を生成するために、あるコードの__FILE__や__LINE__が必要な場合もあります。以下は、Visual C++のクラシックです。
#define WRNG_PRIVATE_STR2(z) #z
#define WRNG_PRIVATE_STR1(x) WRNG_PRIVATE_STR2(x)
#define WRNG __FILE__ "("WRNG_PRIVATE_STR1(__LINE__)") : ------------ : "
次のコードと同様に:
#pragma message(WRNG "Hello World")
次のようなメッセージを生成します。
C:\my_project\my_cpp_file.cpp (225) : ------------ Hello World
また、プロパティのゲッターとセッターを生成するなど、#および##連結演算子を使用してコードを生成する必要があります(これは非常に限られたケースです)。
また、次のように関数を使用してコンパイルするとコンパイルされないコードを生成する場合もあります。
#define MY_TRY try{
#define MY_CATCH } catch(...) {
#define MY_END_TRY }
として使用できます
MY_TRY
doSomethingDangerous() ;
MY_CATCH
tryToRecoverEvenWithoutMeaningfullInfo() ;
damnThoseMacros() ;
MY_END_TRY
(まだ、この種のコードが正しく使用されているのを見ただけですonce)
最後になりましたが、有名な boost::foreach
!!!
#include <string>
#include <iostream>
#include <boost/foreach.hpp>
int main()
{
std::string hello( "Hello, world!" );
BOOST_FOREACH( char ch, hello )
{
std::cout << ch;
}
return 0;
}
(注:ブーストホームページからコードをコピー/貼り付け)
std::for_each
よりも(IMHO)方法が優れています。
したがって、マクロは通常のコンパイラ規則の外にあるため、常に便利です。しかし、私が見たほとんどの場合、それらは事実上、適切なC++に翻訳されていないCコードのままです。
Cプリプロセッサを恐れることは、蛍光灯を手に入れたというだけで白熱電球を恐れるようなものです。はい、前者は{電気|プログラマーの時間}はい、彼らによって(文字通り)火傷をすることができます。しかし、適切に処理すれば、彼らは仕事を成し遂げることができます。
組み込みシステムをプログラムするとき、Cは、フォームアセンブラとは別の唯一のオプションとして使用されます。 C++を使用してデスクトップでプログラミングしてから、より小さい組み込みターゲットに切り替えると、非常に多くのベアC機能(マクロを含む)の「不正」を心配するのをやめ、それらから得られる最良で安全な使用法を見つけようとします特徴。
アレクサンダー・ステパノフ says :
C++でプログラミングする場合、そのCの遺産を恥じるべきではありませんが、それを最大限に活用してください。 C++の唯一の問題、さらにはCの唯一の問題は、それら自体が独自のロジックと矛盾している場合に発生します。
__FILE__
および__LINE__
マクロは、QAインフラストラクチャの自動ログファイルスキャナーと共に、情報豊富な例外のスロー、キャッチ、およびログの診断目的で使用します。
たとえば、スローマクロOUR_OWN_THROW
は、テキストの説明を含む、その例外の例外タイプとコンストラクターパラメーターと共に使用される場合があります。このような:
OUR_OWN_THROW(InvalidOperationException, (L"Uninitialized foo!"));
もちろん、このマクロは、コンストラクターパラメーターとして説明を含むInvalidOperationException
例外をスローしますが、スローが発生したファイル名と行番号とそのテキストの説明で構成されるメッセージをログファイルに書き込みます。スローされた例外はIDを取得し、これもログに記録されます。例外がコード内のどこかでキャッチされた場合、そのようにマークされ、ログファイルはその特定の例外が処理されたことを示すため、後でログオンする可能性のあるクラッシュの原因ではない可能性があります。未処理の例外は、自動化されたQAインフラストラクチャによって簡単に検出できます。
コードの繰り返し。
ブーストプリプロセッサライブラリ をご覧ください。これは一種のメタメタプログラミングです。トピック->動機で、良い例を見つけることができます。
プリプロセッサ(マクロ)を使用して、非常に高度で有用なものを構築することができますが、テンプレートを含むc ++の「言語構造」を使用することはできません。
例:
時々マクロを使用して、情報を1か所で定義できるようにしますが、コードのさまざまな部分でさまざまな方法で使用します。それはほんの少し悪です:)
たとえば、「field_list.h」の場合:
/*
* List of fields, names and values.
*/
FIELD(EXAMPLE1, "first example", 10)
FIELD(EXAMPLE2, "second example", 96)
FIELD(ANOTHER, "more stuff", 32)
...
#undef FIELD
次に、パブリック列挙型の場合、名前を使用するように定義できます。
#define FIELD(name, desc, value) FIELD_ ## name,
typedef field_ {
#include "field_list.h"
FIELD_MAX
} field_en;
また、プライベート初期化関数では、すべてのフィールドを使用してテーブルにデータを入力できます。
#define FIELD(name, desc, value) \
table[FIELD_ ## name].desc = desc; \
table[FIELD_ ## name].value = value;
#include "field_list.h"
何かのようなもの
void debugAssert(bool val, const char* file, int lineNumber);
#define assert(x) debugAssert(x,__FILE__,__LINE__);
あなたがちょうどできるように
assert(n == true);
nがfalseの場合、ログに出力された問題のソースファイル名と行番号を取得します。
次のような通常の関数呼び出しを使用する場合
void assert(bool val);
マクロの代わりに取得できるのは、アサート関数の行番号がログに出力されることだけです。これはあまり有用ではありません。
一般的な用途の1つは、コンパイル環境を検出することです。クロスプラットフォーム開発では、たとえば、目的のクロスプラットフォームライブラリがまだ存在しない場合、Linux用のコードセットとWindows用のコードセットを記述できます。
そのため、大まかな例では、クロスプラットフォームミューテックスは
void lock()
{
#ifdef WIN32
EnterCriticalSection(...)
#endif
#ifdef POSIX
pthread_mutex_lock(...)
#endif
}
関数の場合、タイプセーフを明示的に無視する場合に便利です。 ASSERTを実行するための上記および下の多くの例など。もちろん、C/C++の多くの機能と同様に、自分で足を踏み入れることができますが、言語はツールを提供し、何をすべきかを決定できるようにします。
#define ARRAY_SIZE(arr) (sizeof arr / sizeof arr[0])
現在のスレッドで説明されている「優先」テンプレートソリューションとは異なり、定数式として使用できます。
char src[23];
int dest[ARRAY_SIZE(src)];
マクロを使用して、例外を簡単に定義します。
DEF_EXCEPTION(RessourceNotFound, "Ressource not found")
ここで、DEF_EXCEPTIONは
#define DEF_EXCEPTION(A, B) class A : public exception\
{\
public:\
virtual const char* what() const throw()\
{\
return B;\
};\
}\
#definesを使用して、デバッグと単体テストのシナリオを支援できます。たとえば、メモリ関数の特別なロギングバリアントを作成し、特別なmemlog_preinclude.hを作成します。
#define malloc memlog_malloc
#define calloc memlog calloc
#define free memlog_free
次を使用してコードをコンパイルします。
gcc -Imemlog_preinclude.h ...
Memlog.o内の最終画像へのリンク。おそらくロギングの目的で、または単体テストの割り当てエラーをシミュレートするために、mallocなどを制御するようになりました。
コンパイラーは、インラインへの要求を拒否できます。
マクロには常に場所があります。
デバッグトレース用に#define DEBUGを使用すると便利です。問題のデバッグ中は1のままにして(または開発サイクル全体でオンのままにして)、出荷時にオフにすることができます。
コンパイラ/ OS /ハードウェア固有の動作をコンパイル時に決定するとき。
Comppiler/OS/Hardware固有の機能へのインターフェイスを作成できます。
#if defined(MY_OS1) && defined(MY_HARDWARE1)
#define MY_ACTION(a,b,c) doSothing_OS1HW1(a,b,c);}
#Elif define(MY_OS1) && defined(MY_HARDWARE2)
#define MY_ACTION(a,b,c) doSomthing_OS1HW2(a,b,c);}
#Elif define(MY_SUPER_OS)
/* On this hardware it is a null operation */
#define MY_ACTION(a,b,c)
#else
#error "PLEASE DEFINE MY_ACTION() for this Compiler/OS/HArdware configuration"
#endif
VA_ARGSはこれまで間接的に言及されているようです。
ジェネリックC++ 03コードを記述するときに、可変数の(ジェネリック)パラメーターが必要な場合は、テンプレートの代わりにマクロを使用できます。
#define CALL_RETURN_WRAPPER(FnType, FName, ...) \
if( FnType theFunction = get_op_from_name(FName) ) { \
return theFunction(__VA_ARGS__); \
} else { \
throw invalid_function_name(FName); \
} \
/**/
注:一般に、名前のチェック/スローは仮想のget_op_from_name
関数に組み込むこともできます。これは単なる例です。 VA_ARGS呼び出しを囲む他の汎用コードが存在する場合があります。
C++ 11で可変長テンプレートを取得したら、テンプレートでこれを「適切に」解決できます。
たぶん、マクロの素晴らしい使い方はプラットフォームに依存しない開発にあります。型の不整合の場合を考えてください-マクロを使用すると、次のような異なるヘッダーファイルを使用できます。--WIN_TYPES.H
typedef ...some struct
--POSIX_TYPES.h
typedef ...some another struct
--program.h
#ifdef WIN32
#define TYPES_H "WINTYPES.H"
#else
#define TYPES_H "POSIX_TYPES.H"
#endif
#include TYPES_H
私の意見では、他の方法で実装するよりもはるかに読みやすい。
私の最後の仕事で、私はウイルススキャナーに取り組んでいました。デバッグを簡単にするために、大量のロギングがいたるところに行き詰っていましたが、そのような需要の高いアプリでは、関数呼び出しの費用が高すぎます。だから、この小さなマクロを思いついたので、顧客サイトのリリースバージョンでデバッグロギングを有効にすることができました。関数呼び出しのコストなしでデバッグフラグをチェックし、何も記録せずに戻るか、有効になっている場合、ロギングを行います...マクロは次のように定義されました。
#define dbgmsg(_FORMAT, ...) if((debugmsg_flag & 0x00000001) || (debugmsg_flag & 0x80000000)) { log_dbgmsg(_FORMAT, __VA_ARGS__); }
ログ関数のVA_ARGSのため、これはこのようなマクロの良いケースでした。
その前に、適切なアクセス権を持っていないことをユーザーに伝える必要がある高セキュリティアプリケーションでマクロを使用しました。
定義されたマクロ:
#define SECURITY_CHECK(lRequiredSecRoles) if(!DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, true)) return
#define SECURITY_CHECK_QUIET(lRequiredSecRoles) (DoSecurityCheck(lRequiredSecRoles, #lRequiredSecRoles, false))
次に、UI全体にチェックを振りかけるだけで、そのロールがまだない場合は、どのロールがあなたがしようとしたアクションを実行することを許可されているかがわかります。 2つの理由は、ある場所では値を返し、他の場所ではvoid関数から戻ることでした...
SECURITY_CHECK(ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR);
LRESULT CAddPerson1::OnWizardNext()
{
if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_EMPLOYEE) {
SECURITY_CHECK(ROLE_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD ) -1;
} else if(m_Role.GetItemData(m_Role.GetCurSel()) == parent->ROLE_CONTINGENT) {
SECURITY_CHECK(ROLE_CONTINGENT_WORKER_ADMINISTRATOR | ROLE_BUSINESS_INFORMATION_STEWARD | ROLE_WORKER_ADMINISTRATOR) -1;
}
...
とにかく、それは私がそれらをどのように使用したかであり、これがテンプレートでどのように役立つかはわかりません...それ以外は、本当に必要でない限り、それらを避けるようにします。
さらに別のforeachマクロ。 T:タイプ、c:コンテナー、i:イテレーター
#define foreach(T, c, i) for(T::iterator i=(c).begin(); i!=(c).end(); ++i)
#define foreach_const(T, c, i) for(T::const_iterator i=(c).begin(); i!=(c).end(); ++i)
使用法(概念ではなく、実際の表示):
void MultiplyEveryElementInList(std::list<int>& ints, int mul)
{
foreach(std::list<int>, ints, i)
(*i) *= mul;
}
int GetSumOfList(const std::list<int>& ints)
{
int ret = 0;
foreach_const(std::list<int>, ints, i)
ret += *i;
return ret;
}
より良い実装が利用可能:Google "BOOST_FOREACH"
利用可能な良い記事:条件付き愛:FOREACH Redux(Eric Niebler) http://www.artima.com/cppsource/foreach.html
プリプロセッサを使用して、コンパイルされたコードで浮動小数点を使用できない組み込みシステムで使用される浮動小数点値から固定小数点数を計算しました。数学のすべてを実世界単位で行い、それらを固定小数点で考える必要がないようにすると便利です。
例:
// TICKS_PER_UNIT is defined in floating point to allow the conversions to compute during compile-time.
#define TICKS_PER_UNIT 1024.0
// NOTE: The TICKS_PER_x_MS will produce constants in the preprocessor. The (long) cast will
// guarantee there are no floating point values in the embedded code and will produce a warning
// if the constant is larger than the data type being stored to.
// Adding 0.5 sec to the calculation forces rounding instead of truncation.
#define TICKS_PER_1_MS( ms ) (long)( ( ( ms * TICKS_PER_UNIT ) / 1000 ) + 0.5 )
たくさんのものに使用されるフィールドのリストがある場合、例えば構造の定義、その構造のバイナリ形式とのシリアル化、データベースの挿入などを行うと、(再帰的に!)プリプロセッサを使用してフィールドリストの繰り返しを回避できます。
これは明らかに恐ろしいことです。しかし、複数の場所にある長いフィールドのリストを更新するよりも良い場合がありますか?私はこの手法を1回だけ使用しましたが、それは非常に役立ちました。
もちろん、同じ一般的な考え方が適切に反映された言語で広く使用されています。クラスを調べて、各フィールドを順番に操作するだけです。 Cプリプロセッサで実行すると、壊れやすく、読みにくく、常に移植性があるとは限りません。だから私はそれをいくつかの恐怖で言及します。それにもかかわらず、ここにある...
(編集:これは、9/18で@Andrew Johnsonが言ったことに似ていることがわかりましたが、同じファイルを再帰的に含めるという考え方は、もう少し考えを取ります。)
// file foo.h, defines class Foo and various members on it without ever repeating the
// list of fields.
#if defined( FIELD_LIST )
// here's the actual list of fields in the class. If FIELD_LIST is defined, we're at
// the 3rd level of inclusion and somebody wants to actually use the field list. In order
// to do so, they will have defined the macros STRING and INT before including us.
STRING( fooString )
INT( barInt )
#else // defined( FIELD_LIST )
#if !defined(FOO_H)
#define FOO_H
#define DEFINE_STRUCT
// recursively include this same file to define class Foo
#include "foo.h"
#undef DEFINE_STRUCT
#define DEFINE_CLEAR
// recursively include this same file to define method Foo::clear
#include "foo.h"
#undef DEFINE_CLEAR
// etc ... many more interesting examples like serialization
#else // defined(FOO_H)
// from here on, we know that FOO_H was defined, in other words we're at the second level of
// recursive inclusion, and the file is being used to make some particular
// use of the field list, for example defining the class or a single method of it
#if defined( DEFINE_STRUCT )
#define STRING(a) std::string a;
#define INT(a) long a;
class Foo
{
public:
#define FIELD_LIST
// recursively include the same file (for the third time!) to get fields
// This is going to translate into:
// std::string fooString;
// int barInt;
#include "foo.h"
#endif
void clear();
};
#undef STRING
#undef INT
#endif // defined(DEFINE_STRUCT)
#if defined( DEFINE_ZERO )
#define STRING(a) a = "";
#define INT(a) a = 0;
#define FIELD_LIST
void Foo::clear()
{
// recursively include the same file (for the third time!) to get fields.
// This is going to translate into:
// fooString="";
// barInt=0;
#include "foo.h"
#undef STRING
#undef int
}
#endif // defined( DEFINE_ZERO )
// etc...
#endif // end else clause for defined( FOO_H )
#endif // end else clause for defined( FIELD_LIST )
#define
または-D
オプションを使用して、コンパイラコマンドラインで/D
定数を使用できます。これは、複数のプラットフォームで同じソフトウェアをクロスコンパイルする場合に便利です。これは、各プラットフォームで定義される定数をメイクファイルで制御できるためです。
このトリックは、関数でエミュレートできないプリプロセッサの巧妙な使用だと思います:
#define COMMENT COMMENT_SLASH(/)
#define COMMENT_SLASH(s) /##s
#if defined _DEBUG
#define DEBUG_ONLY
#else
#define DEBUG_ONLY COMMENT
#endif
その後、次のように使用できます。
cout <<"Hello, World!" <<endl;
DEBUG_ONLY cout <<"This is outputed only in debug mode" <<endl;
RELEASE_ONLYマクロを定義することもできます。
デバッグビルドで追加のログを有効にし、ブールチェックのオーバーヘッドなしでリリースビルドで無効にすることができます。したがって、代わりに:
void Log::trace(const char *pszMsg) {
if (!bDebugBuild) {
return;
}
// Do the logging
}
...
log.trace("Inside MyFunction");
あなたが持つことができます:
#ifdef _DEBUG
#define LOG_TRACE log.trace
#else
#define LOG_TRACE void
#endif
...
LOG_TRACE("Inside MyFunction");
_DEBUGが定義されていない場合、これはコードをまったく生成しません。プログラムはより高速に実行され、トレースログのテキストは実行可能ファイルにコンパイルされません。
マクロは、switchステートメントの構文をシミュレートするのに役立ちます。
switch(x) {
case val1: do_stuff(); break;
case val2: do_other_stuff();
case val3: yet_more_stuff();
default: something_else();
}
非整数値タイプの場合。この質問では:
switchステートメントで文字列を使用する-C++ 17の立場はどこですか?
ラムダを含むいくつかのアプローチを示唆する答えを見つけることができますが、残念なことに、最も近いのはマクロです:
SWITCH(x)
CASE val1 do_stuff(); break;
CASE val2 do_other_stuff();
CASE val3 yet_more_stuff();
DEFAULT something_else();
END
#define COLUMNS(A,B) [(B) - (A) + 1]
struct
{
char firstName COLUMNS( 1, 30);
char lastName COLUMNS( 31, 60);
char address1 COLUMNS( 61, 90);
char address2 COLUMNS( 91, 120);
char city COLUMNS(121, 150);
};
これをインライン関数として実装できますか?
#define my_free(x) do { free(x); x = NULL; } while (0)
リソースコンパイラはそれらを理解するだけなので、Visual Studioのリソース識別子にはマクロが必要です(つまり、constまたはenumでは機能しません)。