どうも。お久しぶりです。
色々と書きたいこと、やりたいこと、あるんですけども、なんだかんだで就活大変ですね。まあまあ頑張ってます。
それはともかくとして、今回はC++の話です。
とあるSDKがヘッダでusing namespace std;してるって話題を見まして、要は、名前空間を汚す(※)のって良くないよね行儀悪いよねということですよね。でも、名前空間の指定を省略できるようにすれば、冗長さがなくなって実装しやすいわけですから、グローバルな名前空間を汚さずにその恩恵を受けたいものです。たとえば、あるクラスがstd::vector
という話題を投げたら、typedefでできる?って言われまして。あ、できるねと。テンプレート書いてる時ぐらいにしかtypedef使ってなかったので盲点になってました。せっかくなので、usingとtypedefを使ってみて、どんな違いがあるのか確めてみようと思いました。これが今回の内容になります。
※「名前空間を汚す」以外の言い回しが思いつきませんでした。ここでいう「名前空間」は、C++の言語機能である名前空間(namespace)ではなくて、一般的な意味での名前空間です。混乱させてたらごめんなさい。
usingとtypedefはどこで使えるか
どのコードが通るのか、色々と試してみました。コンパイラはVisual Studio 2013(v120)です。
#include <string>
class A {
public:
typedef std::string string; // OK
string s;
};
class B {
typedef std::string string; // OK
public:
string s;
};
class C {
//using std::string; // NG
};
class D {
//using namespace std; // NG
};
namespace E {
using namespace std; // OK
class Hoge {
public:
string s;
};
}
namespace F {
using std::string; // OK
class Hoge {
public:
string s;
};
}
namespace G {
typedef std::string string; // OK
class Hoge {
public:
string s;
};
}
namespace {
typedef A::string AString; // OK
typedef E::string EString; // OK
typedef F::string FString; // OK
typedef G::string GString; // OK
}
int main(void) {
{ // (1)
//string s; // NG
}
{ // (2)
AString a;
EString e;
FString f;
GString g;
}
{ // (3)
A::string s; // OK
}
{ // (4)
//B::string s; // NG
}
{ // (5)
E::string s; // OK
}
{ // (6)
F::string s; // OK
}
{ // (7)
G::string s; // OK
}
{ // (8)
typedef std::string string; // OK
string s;
}
{ // (9)
using std::string; // OK
string s;
}
{ // (10)
//using A::string; // NG
}
{ // (11)
using E::string; // OK
string s;
}
{ // (12)
using F::string; // OK
string s;
}
{ // (13)
using G::string; // OK
string s;
}
{ // (14)
typedef std::string string; // OK
using std::string; // OK
string s;
}
{ // (15)
using std::string; // OK
typedef std::string string; // OK
string s;
}
{ // (16)
//typedef A string; // OK
//using std::string; // NG
}
{ // (17)
//using std::string; // OK
//typedef A string; // NG
}
return 0;
}
見たとおりなのですが、一応、解説します。
まずは各クラスや名前空間を定義して、usingやtypedefの色んなパターンを試しています。ついでに、main()でも色々試して、コンパイルが通るか試しています。説明が雑すぎるけど、まー、これでじゅうぶんだ。
クラスCとDが実現できないことが、事の発端ですね。
(1)まず、何もしないでstd::stringをstringと書けないという当たり前のことを確認してますが、ここのスコープで使えないということが大事です。他に影響しちゃうので。無名名前空間で、usingやらするとこちらでも使えるようになるため、あえてtypedefをAStringとか別の名前にしています。
(2)無名名前空間でのtypedefが使えることを確認。もちろん、usingも反映されますが、名前変えられないし、名前空間汚したくないのでここでは避けてます。無名名前空間使うときは気をつけましょう。
(3)publicでtypedefしたstringは、クラス外でもA::stringという形で利用できるという例です。
(4)privateはダメです。クラス外にtypedefを隠したければ、こちらを使いましょう。
(5)(6)(7)名前空間のスコープ内でusing指令/宣言とtypedefしたケースですね。こちらも、(2)のようなアクセスが可能です。
(8)(9)スコープ内でのusing宣言とtypedefは似てますね。ここでusing namespaceの確認はわかってるので別にいらないですよね。
(10)(11)(12)(13)面白いことになりました。(10)のA::stringはAが名前空間ではなくクラスだからダメなようです。using宣言はあくまで名前空間のショートカットを作るものという感じですかね。
(14)(15)同じスコープで同一対象をusing宣言およびtypedefしてみたら大丈夫でした。指すものが同じならば、曖昧性はないということですね。
(16)(17)指すものが異なればダメです。当たり前です。
結論としては、typedefはクラス宣言内に含むことができて、そこで与えた別名は内部クラスみたいな感じで型名にアクセスできます。public/privateといったアクセスレベルも反映されます。
一方、using宣言は限定的なtypedefという感じで、スコープ内に記述して、クラス名は変更できないけど名前空間の省略を可能にします。クラス名の省略はできません。typedefと衝突しても、対象が同じであればコンパイルが通ります。
エイリアス
参照のことではなく、名前空間のエイリアス。型名ではなく名前空間を対象にしたtypedefという感じ。使ったことない。
namespace veryverylongnamespace {
const int SOMETHING_SIZE = 10;
}
namespace shortname = veryverylongnamespace;
int main(void) {
return shortname::SOMETHING_SIZE;
}
namespace X = A::B::C;みたいなことをするかどうか(ただし、Cは名前空間)。typedefは型名が対象なので、置き換えできないんですよね。
typedefの置き換え
C++0x(C++11)で導入されたalias declaration(エイリアス宣言と言っていいのかな)がつおい。
using type1 = int ;
using type2 = int [10] ;
using type3 = type1 (*) ( type2 ) ;
template<T> using A = B<T>;という感じで、普通にテンプレートも扱えます。これに関しては、見た目のすっきりしたtypedefと思っていいみたいです。alias declarationはusing宣言とは異なり、クラス定義内に含むことができ、アクセスレベルも反映されます。古い環境でないなら、もうこれでいいですね。
C++11/14ちゃんと勉強しないとなぁ。