エイリアスと typedef (C++)

エイリアスの宣言を使用して、以前に宣言した型のシノニムとして使用する名前を宣言できます (このメカニズムは非公式には型エイリアスとも呼ばれます)。 このメカニズムを使用してエイリアス テンプレート作成することもできます。これは、カスタム アロケーターに役立ちます。

構文

using identifier = type;

解説

識別子
エイリアスの名前。

type
エイリアスを作成する型識別子。

エイリアスは新しい型を導入せず、既存の型名の意味を変更することはできません。

エイリアスの最もシンプルな形式は C++03 の typedef のメカニズムと同等です。

// C++11
using counter = long;

// C++03 equivalent:
// typedef long counter;

どちらの形式でも、型 counterの変数を作成できます。 次のような std::ios_base::fmtflags の型エイリアスが便利な場合があります。

// C++11
using fmtfl = std::ios_base::fmtflags;

// C++03 equivalent:
// typedef std::ios_base::fmtflags fmtfl;

fmtfl fl_orig = std::cout.flags();
fmtfl fl_hex = (fl_orig & ~std::cout.basefield) | std::cout.showbase | std::cout.hex;
// ...
std::cout.flags(fl_hex);

エイリアスは関数ポインターでも機能しますが、同等の typedef よりはるかに読みやすくなります。

// C++11
using func = void(*)(int);

// C++03 equivalent:
// typedef void (*func)(int);

// func can be assigned to a function pointer value
void actual_function(int arg) { /* some code */ }
func fptr = &actual_function;

typedef メカニズムでは、テンプレートでは機能しないという制限があります。 しかし、C++11 での型エイリアスの構文ではエイリアス テンプレートを作成できます。

template<typename T> using ptr = T*;

// the name 'ptr<T>' is now an alias for pointer to T
ptr<int> ptr_int;

次の例に、エイリアス テンプレートをカスタム アロケーター (この場合は整数ベクター型) で使用する方法を示します。 int を任意の型に置き換えて、便利なエイリアスを作成することで、主要な機能コード内の複雑なパラメーター リストを隠ぺいできます。 コード全体でカスタム アロケーターを使用することで、読みやすさを向上させ、入力ミスに起因するバグが発生するリスクを軽減できます。

#include <stdlib.h>
#include <new>

template <typename T> struct MyAlloc {
    typedef T value_type;

    MyAlloc() { }
    template <typename U> MyAlloc(const MyAlloc<U>&) { }

    bool operator==(const MyAlloc&) const { return true; }
    bool operator!=(const MyAlloc&) const { return false; }

    T * allocate(const size_t n) const {
        if (n == 0) {
            return nullptr;
        }

        if (n > static_cast<size_t>(-1) / sizeof(T)) {
            throw std::bad_array_new_length();
        }

        void * const pv = malloc(n * sizeof(T));

        if (!pv) {
            throw std::bad_alloc();
        }

        return static_cast<T *>(pv);
    }

    void deallocate(T * const p, size_t) const {
        free(p);
    }
};

#include <vector>
using MyIntVector = std::vector<int, MyAlloc<int>>;

#include <iostream>

int main ()
{
    MyIntVector foov = { 1701, 1764, 1664 };

    for (auto a: foov) std::cout << a << " ";
    std::cout << "\n";

    return 0;
}
1701 1764 1664

Typedefs

typedef 宣言は、スコープ内で、宣言の type-declaration 部分で指定された型のシノニムになる名前を導入します。

typedef 宣言を使用すると、言語によって既に定義されている型または宣言した型に対して、より短い、または意味のある名前を作成できます。 Typedef 名を使用して、変更可能な実装の詳細をカプセル化できます。

、およびenum宣言とは対照的にunionclassstructtypedef宣言では新しい型は導入されません。既存の型には新しい名前が導入されます。

typedef を使用して宣言された名前は他の識別子と同じ名前空間を使用します (ステートメント ラベルを除く)。 そのため、クラス型宣言を除き、以前に宣言した名前と同じ識別子を使用することはできません。 次の例を考えてみましょう。

// typedef_names1.cpp
// C2377 expected
typedef unsigned long UL;   // Declare a typedef name, UL.
int UL;                     // C2377: redefined.

他の識別子に関連する名前隠ぺいルールも、typedef を使用して宣言する名前の可視性を制御します。 したがって、次の例は C++ で有効です。

// typedef_names2.cpp
typedef unsigned long UL;   // Declare a typedef name, UL
int main()
{
   unsigned int UL;   // Redeclaration hides typedef name
}

// typedef UL back in scope

名前非表示の別のインスタンス:

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

ローカル スコープ識別子を同じ名前 typedefで宣言する場合、または構造体または共用体のメンバーを同じスコープまたは内部スコープで宣言する場合は、型指定子を指定する必要があります。 次に例を示します。

typedef char FlagType;
const FlagType x;

識別子、構造体メンバー、または共用体メンバーに対して FlagType 名を再利用するには、次のように型を指定する必要があります。

const int FlagType;  // Type specifier required

次のような記述だけでは不十分です

const FlagType;      // Incomplete specification

これは、 FlagType 再宣言されている識別子ではなく、型の一部と見なされるためです。 この宣言は、次のような違法な宣言と見なされます。

int;  // Illegal declaration

ポインター、関数、配列型を含め、あらゆる型を typedef で宣言できます。 構造体型または共用体型を定義する前に、構造体型または共用体型へのポインターの typedef 名を宣言できます。ただし、定義が宣言と同じ可視性である必要があります。

typedef 宣言の使用方法の 1 つは、宣言をより同型でコンパクトにすることです。 次に例を示します。

typedef char CHAR;          // Character type.
typedef CHAR * PSTR;        // Pointer to a string (char *).
PSTR strchr( PSTR source, CHAR target );
typedef unsigned long ulong;
ulong ul;     // Equivalent to "unsigned long ul;"

typedef を使用して同じ宣言で基本型と派生型を指定するには、宣言子をコンマで区切ります。 次に例を示します。

typedef char CHAR, *PSTR;

次の例は、値を返さず、2 つの int 引数を受け取る関数に対する型 DRAWF を用意します。

typedef void DRAWF( int, int );

上記 typedef のステートメントの後、その宣言

DRAWF box;

は次の宣言と同等です。

void box( int, int );

typedef は、多くの場合、ユーザー定義型を宣言し、名前を付けるために、次のように struct と組み合わせて使用されます。

// typedef_specifier2.cpp
#include <stdio.h>

typedef struct mystructtag
{
    int   i;
    double f;
} mystruct;

int main()
{
    mystruct ms;
    ms.i = 10;
    ms.f = 0.99;
    printf_s("%d   %f\n", ms.i, ms.f);
}
10   0.990000

typedef の再宣言

typedef 宣言を使用すると、同じ名前を再宣言して同じ型を参照することができます。 次に例を示します。

ソース ファイル file1.h:

// file1.h
typedef char CHAR;

ソース ファイル file2.h:

// file2.h
typedef char CHAR;

ソース ファイル prog.cpp:

// prog.cpp
#include "file1.h"
#include "file2.h"   // OK

このファイルprog.cppには 2 つのヘッダー ファイルが含まれ、両方に名前の宣言が含まれていますtypedefCHAR。 両方の宣言が同じ型を参照する限り、このような再宣言は許容されます。

typedef以前に別の型として宣言された名前を再定義することはできません。 次の代替手段 file2.hを検討してください。

// file2.h
typedef int CHAR;     // Error

名前を再CHAR宣言して別の型を参照しようとしたため、コンパイラはエラーprog.cppを発行します。 このポリシーは、次のようなコンストラクトに拡張されます。

typedef char CHAR;
typedef CHAR CHAR;      // OK: redeclared as same type

typedef union REGS      // OK: name REGS redeclared
{                       //  by typedef name with the
    struct wordregs x;  //  same meaning.
    struct byteregs h;
} REGS;

C++ と C の typedef

クラス型を持つ typedef 指定子の使用がサポートされているのは、主に、typedef 宣言内で名前のない構造体を宣言する ANSI C に対応するためです。 たとえば、多くの C プログラマは次のイディオムを使用します。

// typedef_with_class_types1.cpp
// compile with: /c
typedef struct {   // Declare an unnamed structure and give it the
                   // typedef name POINT.
   unsigned x;
   unsigned y;
} POINT;

このような宣言の利点は、次のような宣言を使用できることです。

POINT ptOrigin;

次のように記述する必要がありません。

struct point_t ptOrigin;

C++ では、typedef 名と実際の型 (classstructunion、および enum のキーワードで宣言) との違いがより明確です。 typedef ステートメント内で無名の構造体を宣言する C 言語の記述法も使用できますが、C 言語に見られる表記の利点は得られません。

// typedef_with_class_types2.cpp
// compile with: /c /W1
typedef struct {
   int POINT();
   unsigned x;
   unsigned y;
} POINT;

前の例では、名前のないクラス typedef 構文を使用して、POINT という名前のクラスを宣言しています。 POINT はクラス名として扱われますが、この方法で導入された名前には次の制限が適用されます。

  • 名前 (シノニム) は、プレフィックスのunion後にstructclass表示できません。

  • 名前は、クラス宣言内でコンストラクター名またはデストラクター名として使用することはできません。

要約すると、この構文では、継承、構築、または破棄のメカニズムは提供されません。