typedefsおよび#defines


32

私たちは皆、間違いなくtypedefsと#definesを使用しました。今日、彼らと仕事をしている間、私は物事について熟考し始めました。

int別の名前のデータ型を使用するには、以下の2つの状況を考慮してください。

typedef int MYINTEGER

そして

#define MYINTEGER int

上記の状況のように、多くの状況で、#defineを使用して非常にうまく処理でき、typedefを使用して同じことを実行できますが、同じことを行う方法はまったく異なる場合があります。#defineは、typedefではできないMACROアクションも実行できます。

それらを使用する基本的な理由は異なりますが、動作はどの程度異なりますか?両方を使用できる場合に、一方が他方よりも優先される場合 また、一方の状況では、他方よりも高速であることが保証されていますか?(たとえば、#defineはプリプロセッサディレクティブであるため、すべてがコンパイルまたは実行時よりも早く実行されます)。


8
#defineが何のために設計されているかを使用してください。コードの作成時にはわからないプロパティに基づく条件付きコンパイル(OS /コンパイラなど)。それ以外の場合は、言語構造を使用します。
マーティンヨーク

回答:


67

typedefマクロが特に必要であるという奇妙な理由がない限り、A が一般的に好まれます。

マクロはテキストの置換を行いますが、これはコードのセマンティクスに対してかなりの暴力を及ぼす可能性があります。たとえば、次の場合:

#define MYINTEGER int

あなたは合法的に書くことができます:

short MYINTEGER x = 42;

short MYINTEGER展開されるためshort int

一方、typedefの場合:

typedef int MYINTEGER:

名前MYINTEGERtypeの 別の名前でありint、キーワード「int」のテキスト置換ではありません。

より複雑な型では事態はさらに悪化します。たとえば、次の場合:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

ab、およびcすべてのポインタですが、dあるchar最後の行がに展開するので、:

char* c, d;

これは

char *c;
char d;

(ポインター型のTypedefは通常、良いアイデアではありませんが、これはポイントを示しています。)

別の奇妙なケース:

#define DWORD long
DWORD double x;     /* Huh? */

1
再び非難するポインター!:)このようなマクロを使用するのが賢明ではないポインタ以外の例はありますか?
c0da

2
私がaの代わりにマクロを使用することはありませんtypedefが、引数を使用することで、次の処理を実行するマクロを作成する適切な方法があります#define CHAR_PTR(x) char *x。これにより、コンパイラが正しく使用されないと、少なくともkvetchが発生します。
Blrfl

1
忘れてはいけない:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
ジョン・パーディ

3
定義もエラーメッセージを理解できない
トーマスボニーニ

マクロの誤用が引き起こす可能性のある痛みの例(マクロとtypedefには直接関係ありませんが):github.com/Keith-S-Thompson/42
キーストンプソン

15

マクロの最大の問題は、マクロがスコープされていないことです。それだけでtypedefの使用が保証されます。また、意味的にも明確です。あなたのコードを読む人が定義を見ると、彼はそれを読んでマクロ全体を理解するまでそれが何であるかを知りません。typedefは、タイプ名が定義されることをリーダーに伝えます。(C ++について話していることを言及する必要があります。Cのtypedefスコープについてはわかりませんが、似ていると思います)。


しかし、質問で提供された例で示したように、typedefと#defineは同じ単語数を使用します!また、マクロのこれら3つの単語は混乱を引き起こしません。単語は同じであるが、再配置されているためです。:)
c0da

2
はい、でもそれは短さではありません。マクロは、型定義以外にも多くのことを実行できます。しかし、個人的には、スコープが最も重要だと思います。
タマスシェレイ

2
@ c0da-typedefにはマクロを使用しないでください。typedefにはtypedefを使用してください。マクロの実際の効果は、さまざまな応答によって示されるように、非常に異なる場合があります。
ジョリスティマーマンズ

15

ソースコードは主に仲間の開発者向けに書かれています。コンピューターはコンパイルされたバージョンを使用します。

この観点でtypedefは、意味はあり#defineません。


6

キース・トンプソンの答えは非常に良く、スコーピングについてのタマス・ゼレイの追加のポイントと一緒に、あなたが必要とするすべての背景を提供するはずです。

マクロは必死の最後の手段であると常に考えるべきです。マクロでしかできないことはいくつかあります。それでも、本当にやりたいのなら、長くて一生懸命に考えるべきです。微妙に壊れたマクロによって引き起こされる可能性のあるデバッグの苦痛はかなりのものであり、前処理されたファイルを調べる必要があります。C ++の問題の範囲を把握するために、一度だけ実行する価値があります。前処理されたファイルのサイズだけが目を見張るものになります。


5

既に指定されているスコープとテキスト置換に関するすべての有効なコメントに加えて、#defineはtypedefと完全に互換性がありません!つまり、関数ポインタの場合になります。

typedef void(*fptr_t)(void);

これfptr_tは、その型の関数へのポインタである型を宣言しvoid func(void)ます。

このタイプをマクロで宣言することはできません。#define fptr_t void(*)(void)明らかに動作しません。のようなあいまいなものを書く必要が#define fptr_t(name) void(*name)(void)ありますが、コンストラクタがないC言語では実際には意味がありません。

配列ポインターは、#defineでも宣言できません。 typedef int(*arr_ptr)[10];


また、Cには言及する価値のあるタイプセーフティの言語サポートはありませんが、typedefと#defineに互換性がない別のケースは、疑わしいタイプ変換を行う場合です。typedefを使用すると、コンパイラーや静的アナライザーツールは、そのような変換に対して警告を出すことができます。


1
さらに良いのは、関数と配列ポインターの組み合わせですchar *(*(*foo())[])();(のようfooにポインターを返す関数へのポインターの配列へのポインターを返す関数charです)。
ジョンボード

4

ジョブを実行するのに必要な電力が最も少なく、警告が最も多いツールを使用します。#defineはプリプロセッサで評価されますが、ほとんどの場合はそこにいます。typedefはコンパイラーによって評価されます。名前が示すように、チェックが行われ、typedefはタイプのみを定義できます。したがって、あなたの例では間違いなくtypedefに行きます。


2

定義に対する通常の引数を超えて、マクロを使用してこの関数をどのように記述しますか?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

これは明らかに些細な例ですが、その関数は、加算を実装する型の前方反復可能なシーケンスを合計できます*。このように使用されるTypedefは、Generic Programmingの重要な要素です。

*私はこれをここに書いただけで、読者のためのエクササイズとしてコンパイルしておきます:-)

編集:

この答えは多くの混乱を引き起こしているように思われるので、もっと詳しく説明させてください。STLベクトルの定義の内部を見ると、次のようなものが表示されます。

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

標準コンテナ内でtypedefを使用すると、汎用関数(上記で作成した関数など)がこれらの型を参照できます。関数「Sum」はstd::vector<int>、コンテナ内に保持されているタイプ()ではなく、コンテナのタイプ()にテンプレート化されていintます。typedefがなければ、その内部型を参照することはできません。

したがって、typedefはModern C ++の中心であり、これはマクロでは不可能です。


質問はtypedef、マクロ対インライン関数ではなく、マクロ対について尋ねました。
ベンフォークト

確かに、これはtypedefsでのみ可能です...それがvalue_typeが何であるかです。
クリスピットマン

これはtypedefではなく、テンプレートプログラミングです。関数またはファンクターは、それがどれほど一般的であっても型ではありません。

@Lundinさらに詳細を追加しました:関数Sumは、標準コンテナがtypedefを使用してテンプレートされている型を公開するためにのみ可能です。Sumがtypedefであると言っているのではありませんstd :: vector <T> :: value_typeはtypedef だと言っています。検証するために、標準ライブラリの実装のソースを見てください。
クリスピットマン

けっこうだ。おそらく、2番目のスニペットのみを投稿した方が良いでしょう。

1

typedefC ++の哲学:コンパイル時に可能なすべてのチェック/アサートに適合します。#defineコンパイラに多くのセマンティックを隠すプリプロセッサのトリックです。コードの正確性よりもコンパイルのパフォーマンスについて心配する必要はありません。

新しい型を作成するとき、プログラムのドメインが操作する新しい「もの」を定義しています。したがって、この「もの」を使用して関数とクラスを構成し、コンパイラーを使用して静的チェックを行うことができます。とにかく、C ++はCと互換性があるため、int警告を生成しないintベースの型と型との間の暗黙的な変換が多数あります。そのため、この場合、静的チェックのすべての機能を利用することはできません。ただし、をに置き換えてtypedefenum暗黙的な変換を見つけることができます。例えば:あなたがしている場合typedef int Age;、その後、あなたと置き換えることができenum Age { };、あなたが間の暗黙的な変換でエラーのすべての種類を取得しますAgeint

別のこと:はのtypedef中にありnamespaceます。


1

typedefの代わりにdefineを使用することは、別の重要な側面、つまり型特性の概念の下で厄介です。特定のtypedefをすべて定義するさまざまなクラス(標準コンテナのような)を検討してください。typedefを参照することにより、汎用コードを作成できます。この例には、一般的なコンテナ要件(c ++標準23.2.1 [container.requirements.general])が含まれます。

X::value_type
X::reference
X::difference_type
X::size_type

このすべては、スコープを持たないため、マクロを使用して一般的な方法で表現することはできません。


1

デバッガーでこれが意味することを忘れないでください。一部のデバッガーは#defineをうまく処理しません。使用するデバッガーで両方で遊んでください。覚えておいて、あなたはそれを書くよりも読むことに多くの時間を費やすでしょう。


-2

「#define」は後で書いたものを置き換え、typedefはtypeを作成します。したがって、カスタムタイプが必要な場合は、typedefを使用します。マクロが必要な場合は、defineを使用してください。


私はただ投票するだけでなく、コメントを書くことさえしません。
ダイニウス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.