質問の「隠された機能」に関しては、C++が好きではありませんか? 私はそれをそこに捨てるだろうと考えました。C++ の隠れた機能にはどのようなものがありますか?
64 に答える
ほとんどの C++ プログラマーは、三項演算子に精通しています。
x = (y < 0) ? 10 : 20;
しかし、彼らはそれが左辺値として使用できることに気づいていません:
(a == 0 ? a : b) = 1;
これはの省略形です
if (a == 0)
a = 1;
else
b = 1;
注意して使用してください:-)
エラーなしで URI を C++ ソースに入れることができます。例えば:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
私はそこにあるほとんどの投稿に同意します.C++はマルチパラダイム言語であるため、見つけられる「隠された」機能(絶対に避けるべき「未定義の動作」以外)は、機能の巧妙な使用です。
これらの機能のほとんどは、言語の組み込み機能ではなく、ライブラリベースの機能です。
最も重要なのはRAIIです。これは、C の世界から来た C++ 開発者によって何年もの間無視されることがよくありました。演算子のオーバーロードは、多くの場合、配列のような動作 (添字演算子)、ポインターのような操作 (スマート ポインター)、組み込みのような操作 (行列の乗算) の両方を可能にする、誤解されている機能です。
例外の使用はしばしば困難ですが、いくつかの作業により、例外安全仕様 (失敗しないコード、または成功するか元に戻るコミットのような機能を持つコードを含む)を通じて、非常に堅牢なコードを生成できます。元の状態)。
C++ の「隠れた」機能で最も有名なのはテンプレート メタプログラミングです。これにより、実行時ではなくコンパイル時にプログラムを部分的 (または完全に) 実行できるようになります。ただし、これは難しく、試す前にテンプレートをしっかりと把握する必要があります。
C++ の祖先である C の外で「プログラミングの方法」を生み出すために、複数のパラダイムを利用する人もいます。
functorsを使用することで、追加の型安全性とステートフルな関数をシミュレートできます。コマンドパターンを使用すると、コードの実行を遅らせることができます。他のほとんどの設計パターンは、C++ で簡単かつ効率的に実装して、「公式の C++ パラダイム」のリストに含まれていない代替コーディング スタイルを生成できます。
templatesを使用することで、最初は考えていなかったものも含め、ほとんどの型で機能するコードを作成できます。型安全性を高めることもできます (自動化された型安全な malloc/realloc/free のように)。C++ オブジェクトの機能は非常に強力です (したがって、不用意に使用すると危険です) が、動的ポリモーフィズムでさえC++ の静的バージョンであるCRTPを持っています。
Scott Meyersの「Effective C++ 」タイプの本や、Herb Sutter の「 Exceptional C++」タイプの本は、どちらも読みやすく、C++ の既知およびあまり知られていない機能に関する情報の宝庫であることがわかりました。
私の好みの 1 つは、Java プログラマーの髪を恐怖から浮かび上がらせるものです。関数(つまり、クラス メソッド)。理由は次のとおりです。
C++ では、クラスのインターフェイスは、同じ名前空間内のメンバー関数と非メンバー関数の両方です。
非フレンド非メンバー関数には、クラス internal への特権アクセスがありません。そのため、非メンバーの非フレンド関数に対してメンバー関数を使用すると、クラスのカプセル化が弱まります。
これは、経験豊富な開発者でさえも驚くことではありません。
(出典: Herb Sutter のオンライン Guru of the Week #84: http://www.gotw.ca/gotw/084.htm )
在学中ずっと聞いたことがなかったので、多少隠されていると私が考える言語機能の 1 つに名前空間エイリアスがあります。ブーストのドキュメントでその例に出くわすまで、それは私の注意を引くことはありませんでした。もちろん、私がそれについて知ったので、標準の C++ リファレンスで見つけることができます。
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
変数はループのinit部分で宣言できるだけでなく、for
クラスや関数も宣言できます。
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
これにより、異なるタイプの複数の変数が可能になります。
配列演算子は結合的です。
A[8] は *(A + 8) の同義語です。足し算は連想なので *(8 + A) と書き換えることができ、これは ..... 8[A] と同義です。
あなたは有用だとは言いませんでした... :-)
あまり知られていないことの 1 つは、共用体もテンプレートになることができるということです。
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
また、コンストラクターとメンバー関数も持つことができます。継承とは関係ありません(仮想関数を含む)。
C++ は標準であり、隠し機能があってはなりません...
C++ はマルチパラダイム言語です。隠された機能があることに最後のお金を賭けることができます。多くの例のうちの 1 つ:テンプレート メタプログラミング。標準化委員会の誰も、コンパイル時に実行されるチューリング完全なサブ言語があることを意図していませんでした。
Cで機能しないもう1つの隠れた機能は、単項演算子の機能です+
。あなたはそれを使ってあらゆる種類のものを促進し、衰退させることができます
列挙型を整数に変換する
+AnEnumeratorValue
また、以前は列挙型を持っていた列挙型の値が、その値に適合する完全な整数型になりました。手動では、そのタイプはほとんどわかりません。これは、たとえば、列挙にオーバーロードされた演算子を実装する場合に必要です。
変数から値を取得します
クラス外の定義なしでクラス内の静的初期化子を使用するクラスを使用する必要がありますが、リンクに失敗することがありますか?オペレーターは、仮定やそのタイプへの依存関係を作成せずに、一時的なものを作成するのに役立つ場合があります
struct Foo {
static int const value = 42;
};
// This does something interesting...
template<typename T>
void f(T const&);
int main() {
// fails to link - tries to get the address of "Foo::value"!
f(Foo::value);
// works - pass a temporary value
f(+Foo::value);
}
配列をポインタに減衰させます
関数に2つのポインターを渡したいのですが、機能しませんか?オペレーターは助けるかもしれません
// This does something interesting...
template<typename T>
void f(T const& a, T const& b);
int main() {
int a[2];
int b[3];
f(a, b); // won't work! different values for "T"!
f(+a, +b); // works! T is "int*" both time
}
const 参照にバインドされた一時変数の有効期間は、ほとんど知られていません。または少なくとも、ほとんどの人が知らない私のお気に入りの C++ 知識です。
const MyClass& x = MyClass(); // temporary exists as long as x is in scope
あまり使用されない便利な機能は、関数全体の try-catch ブロックです。
int Function()
try
{
// do something here
return 42;
}
catch(...)
{
return -1;
}
主な使用法は、例外を他の例外クラスに変換して再スローするか、例外と戻りベースのエラー コード処理の間で変換することです。
多くの人がidentity
/id
メタ関数を知っていますが、非テンプレートの場合に適したユースケースがあります: 宣言を簡単に書く:
// void (*f)(); // same
id<void()>::type *f;
// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);
// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];
// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;
C++ 宣言の解読に大いに役立ちます。
// boost::identity is pretty much the same
template<typename T>
struct id { typedef T type; };
非常に隠された機能は、if 条件内で変数を定義できることです。そのスコープは、if とその else ブロックにのみ適用されます。
if(int * p = getPointer()) {
// do something
}
たとえば、次のような「ロックされた」スコープを提供するために、一部のマクロはこれを使用します。
struct MutexLocker {
MutexLocker(Mutex&);
~MutexLocker();
operator bool() const { return false; }
private:
Mutex &m;
};
#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else
void someCriticalPath() {
locked(myLocker) { /* ... */ }
}
また、BOOST_FOREACH はボンネットの下でそれを使用します。これを完了するには、if だけでなく、switch でも可能です。
switch(int value = getIt()) {
// ...
}
そしてwhileループで:
while(SomeThing t = getSomeThing()) {
// ...
}
(および for 条件でも)。しかし、これらがすべて役立つかどうかはよくわかりません:)
コンマ演算子が演算子のオーバーロードを呼び出さないようにする
コンマ演算子を有効に使用する場合がありますが、たとえば、左側と右側の間のシーケンスポイントに依存している場合や、目的のオブジェクトに干渉しないようにしたい場合など、ユーザー定義のコンマ演算子が邪魔にならないようにする必要があります。アクション。これがvoid()
ゲームの出番です。
for(T i, j; can_continue(i, j); ++i, void(), ++j)
do_code(i, j);
条件とコードのために配置したプレースホルダーは無視してください。重要なのは、void()
です。これにより、コンパイラは組み込みのコンマ演算子を使用するように強制されます。これは、特性クラスを実装するときにも役立つ場合があります。
コンストラクターでの配列の初期化。たとえば、クラスで次のような配列がある場合int
:
class clName
{
clName();
int a[10];
};
次のように、コンストラクターで配列内のすべての要素をデフォルト (ここでは配列のすべての要素をゼロ) に初期化できます。
clName::clName() : a()
{
}
ああ、代わりにペット嫌いのリストを思いつくことができます:
- 多態的に使用する場合は、デストラクタは仮想である必要があります
- メンバーがデフォルトで初期化される場合と、そうでない場合があります
- ローカルクラスはテンプレートパラメータとして使用できません(有用性が低くなります)
- 例外指定子:便利に見えますが、そうではありません
- 関数のオーバーロードは、異なるシグネチャを持つ基本クラスの関数を非表示にします。
- 国際化に関する有用な標準化はありません(ポータブル標準ワイド文字セット、誰か?C ++ 0xまで待つ必要があります)
プラス面
- 隠された機能:関数tryブロック。残念ながら、私はそれの使用法を見つけていません。はい、なぜ彼らがそれを追加したのかは知っていますが、コンストラクターを再スローする必要があるため、意味がありません。
- コンテナ変更後のイテレータの有効性に関するSTLの保証を注意深く検討する価値があります。これにより、ループを少し改善することができます。
- ブースト-それはほとんど秘密ではありませんが、使用する価値があります。
- 戻り値の最適化(明確ではありませんが、標準で特に許可されています)
- ファンクター、別名関数オブジェクト、別名operator()。これはSTLによって広く使用されています。実際には秘密ではありませんが、演算子のオーバーロードとテンプレートの気の利いた副作用です。
隠された機能:
- 純粋仮想関数は実装を持つことができます。一般的な例、純粋仮想デストラクタ。
関数が例外仕様にリストされていない例外をスローするが、関数の
std::bad_exception
例外仕様にある場合、例外は に変換されstd::bad_exception
、自動的にスローされます。そうすれば、少なくとも abad_exception
がスローされたことを知ることができます。詳細はこちらをご覧ください。関数 try ブロック
クラス テンプレートの typedef のあいまいさを解消するためのテンプレート キーワード。メンバー テンプレートの特殊化の名前が
.
、->
、または::
演算子の後に表示され、その名前が明示的に修飾されたテンプレート パラメーターを持っている場合は、メンバー テンプレート名の前にキーワード template を付けます。詳細はこちらをご覧ください。関数パラメータのデフォルトは実行時に変更できます。詳細はこちらをご覧ください。
A[i]
と同じくらいうまく機能しますi[A]
クラスの一時インスタンスを変更できます! 非 const メンバー関数は、一時オブジェクトで呼び出すことができます。例えば:
struct Bar { void modify() {} } int main (void) { Bar().modify(); /* non-const function invoked on a temporary. */ }
詳細はこちらをご覧ください。
:
三項 ( ) 演算子式の前後に 2 つの異なる型が存在する場合?:
、式の結果の型は 2 つのうち最も一般的な型になります。例えば:void foo (int) {} void foo (double) {} struct X { X (double d = 0.0) {} }; void foo (X) {} int main(void) { int i = 1; foo(i ? 0 : 0.0); // calls foo(double) X x; foo(i ? 0.0 : x); // calls foo(X) }
もう 1 つの隠れた機能は、関数ポインターまたは参照に変換できるクラス オブジェクトを呼び出すことができることです。それらの結果に対してオーバーロードの解決が行われ、引数は完全に転送されます。
template<typename Func1, typename Func2>
class callable {
Func1 *m_f1;
Func2 *m_f2;
public:
callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
operator Func1*() { return m_f1; }
operator Func2*() { return m_f2; }
};
void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }
int main() {
callable<void(int), void(long)> c(foo, bar);
c(42); // calls foo
c(42L); // calls bar
}
これらは「代理呼び出し関数」と呼ばれます。
未定義の動作がなく、期待されるセマンティクスを使用して、保護されたデータと任意のクラスの関数メンバーにアクセスできます。続きを読んで方法を確認してください。これに関する不具合報告もお読みください。
通常、C++ では、そのクラスが基本クラスであっても、クラスのオブジェクトの非静的保護メンバーにアクセスすることは禁止されています。
struct A {
protected:
int a;
};
struct B : A {
// error: can't access protected member
static int get(A &x) { return x.a; }
};
struct C : A { };
それは禁止されています。あなたとコンパイラは、参照が実際に何を指しているのかわかりません。それはC
オブジェクトである可能性があり、その場合、クラスB
にはそのデータに関するビジネスと手がかりがありません。x
このようなアクセスは、 が派生クラスまたは派生クラスへの参照である場合にのみ許可されます。また、次の例のように、メンバーを読み取る「使い捨て」クラスを作成するだけで、コードの任意の部分が保護されたメンバーを読み取ることができますstd::stack
。
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
// error: stack<int>::c is protected
return s.c;
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
確かに、ご覧のとおり、これはあまりにも多くの損傷を引き起こします。しかし今では、メンバー ポインターを使用すると、この保護を回避できます。重要な点は、メンバー ポインターの型が、アドレスを取得するときに指定したクラスではなく、そのメンバーを実際に含むクラスにバインドされることです。これにより、チェックを回避できます
struct A {
protected:
int a;
};
struct B : A {
// valid: *can* access protected member
static int get(A &x) { return x.*(&B::a); }
};
struct C : A { };
もちろん、サンプルでも動作しstd::stack
ます。
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
static std::deque<int> &get(std::stack<int> &s) {
return s.*(pillager::c);
}
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = pillager::get(s);
}
これは、派生クラスで using 宣言を使用するとさらに簡単になります。これにより、メンバー名がパブリックになり、基本クラスのメンバーが参照されます。
void f(std::stack<int> &s) {
// now, let's decide to mess with that stack!
struct pillager : std::stack<int> {
using std::stack<int>::c;
};
// haha, now let's inspect the stack's middle elements!
std::deque<int> &d = s.*(&pillager::c);
}
map::operator[]
キーが欠落している場合にエントリを作成し、デフォルトで作成されたエントリ値への参照を返します。だからあなたは書くことができます:
map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
s.assign(...);
}
cout << s;
これを知らないC++プログラマーの数に驚いています。
関数または変数を名前のない名前空間に配置すると、を使用static
してそれらをファイルスコープに制限することはできなくなります。
クラス テンプレートで通常のフレンド関数を定義するには、特別な注意が必要です。
template <typename T>
class Creator {
friend void appear() { // a new function ::appear(), but it doesn't
… // exist until Creator is instantiated
}
};
Creator<void> miracle; // ::appear() is created at this point
Creator<double> oops; // ERROR: ::appear() is created a second time!
この例では、2 つの異なるインスタンス化が 2 つの同一の定義を作成しています。つまり、ODRに直接違反しています。
したがって、クラス テンプレートのテンプレート パラメーターが、そのテンプレートで定義されている任意のフレンド関数の型で表示されるようにする必要があります (特定のファイルでクラス テンプレートの複数のインスタンス化を防止したい場合を除きますが、これはほとんどありません)。これを前の例のバリエーションに適用してみましょう。
template <typename T>
class Creator {
friend void feed(Creator<T>*){ // every T generates a different
… // function ::feed()
}
};
Creator<void> one; // generates ::feed(Creator<void>*)
Creator<double> two; // generates ::feed(Creator<double>*)
免責事項: C++ Templates: The Complete Guide / Section 8.4からこのセクションを貼り付けました。
void 関数は void 値を返すことができます
あまり知られていませんが、次のコードは問題ありません
void f() { }
void g() { return f(); }
次の奇妙に見えるものだけでなく、
void f() { return (void)"i'm discarded"; }
これを知っていれば、いくつかの分野で活用できます。1 つの例:void
関数は値を返すことはできませんが、何も返さないこともできます。ローカル変数に値を格納するとエラーが発生しますがvoid
、値を直接返すだけです。
template<typename T>
struct sample {
// assume f<T> may return void
T dosomething() { return f<T>(); }
// better than T t = f<T>(); /* ... */ return t; !
};
ファイルを文字列のベクトルに読み込みます。
vector<string> V;
copy(istream_iterator<string>(cin), istream_iterator<string>(),
back_inserter(V));
ビットフィールドをテンプレート化できます。
template <size_t X, size_t Y>
struct bitfield
{
char left : X;
char right : Y;
};
私はまだこれの目的を思い付いていませんが、それは確かに私を驚かせました.
あらゆるプログラミング言語の中で最も興味深い文法の 1 つです。
これらのうちの 3 つは一緒に属し、2 つはまったく異なるものです...
SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));
SomeType
3 番目と 5 番目を除くすべては、スタック上のオブジェクトを定義し、それを初期化します (u
最初の 2 つのケースでは、4 番目ではデフォルトのコンストラクターを使用します。3 番目は、パラメーターをとらず、 a を返す関数を宣言しています。5SomeType
番目も同様に宣言しています。SomeType
という名前の型の値によって 1 つのパラメーターを受け取る関数u
。
優位性ルールは便利ですが、あまり知られていません。基本クラスのラティスを通る一意でないパスであっても、メンバーが仮想基本クラスに属している場合、部分的に非表示のメンバーの名前検索は一意であると言われています。
struct A { void f() { } };
struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };
// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };
これを使用して、優位性ルールによって最も厳密なアラインメントを自動的に判断するアラインメント サポートを実装しました。
これは、仮想関数だけでなく、typedef 名、静的/非仮想メンバーなどにも適用されます。メタプログラムで上書き可能な特性を実装するために使用されるのを見てきました。
前方宣言を取り除く:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
?: 演算子を使用した switch ステートメントの記述:
string result =
a==0 ? "zero" :
a==1 ? "one" :
a==2 ? "two" :
0;
1行ですべてを行う:
void a();
int b();
float c = (a(),b(),1.0f);
memset を使用しない構造体のゼロ設定:
FStruct s = {0};
角度値と時間値の正規化/ラッピング:
int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150
参照の割り当て:
struct ref
{
int& r;
ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;
三項条件演算子?:
では、2 番目と 3 番目のオペランドが「一致する」型 (非公式に言えば) である必要があります。ただし、この要件には例外が 1 つあります (しゃれを意図したものです)。2 番目または 3 番目のオペランドvoid
は、他のオペランドの型に関係なく、throw 式 ( type を持つ) にすることができます。
?:
つまり、演算子を使用して、次の完全に有効な C++ 式を書くことができます。
i = a > b ? a : throw something();
ところで、throw 式が実際には(型のvoid
) 式であり、ステートメントではないという事実は、C++ 言語のもう 1 つのあまり知られていない機能です。これは、とりわけ、次のコードが完全に有効であることを意味します
void foo()
{
return throw something();
}
ただし、この方法ではあまり意味がありません (おそらく、一般的なテンプレート コードでは、これが便利になる場合があります)。
私はこのブログがC++の秘儀についての素晴らしいリソースであることに気づきました:C++Truths。
ローカルクラスは素晴らしいです:
struct MyAwesomeAbstractClass
{ ... };
template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
struct ans : MyAwesomeAbstractClass
{
// Make the implementation depend on T
};
return new ans(...);
}
役に立たないクラス定義で名前空間を汚染しないので、かなりきちんとしています...
危険な秘密は
Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();
めったに使われないお気に入りの秘密:
class A
{
};
struct B
{
A a;
operator A&() { return a; }
};
void func(A a) { }
int main()
{
A a, c;
B b;
a=c;
func(b); //yeah baby
a=b; //gotta love this
}
GCC 開発者にさえ隠されている隠れた機能の 1 つは、文字列リテラルを使用して配列メンバーを初期化することです。C 配列を操作する必要がある構造体があり、配列メンバーをデフォルトの内容で初期化するとします。
struct Person {
char name[255];
Person():name("???") { }
};
これは機能し、char 配列と文字列リテラル初期化子でのみ機能します。いいえstrcpy
、必要ありません!
プリミティブ型にはコンストラクターがあります。
int i(3);
動作します。
多くの例の 1 つ: テンプレートのメタプログラミング。標準化委員会の誰も、コンパイル時に実行されるチューリング完全なサブ言語があることを意図していませんでした。
テンプレートのメタプログラミングは、隠れた機能ではありません。ブーストライブラリにもあります。MPLを参照してください。しかし、「ほぼ非表示」で十分な場合は、ブースト ライブラリを見てください。強力なライブラリの支援がなければ簡単にアクセスできない多くの機能が含まれています。
その一例がboost.lambdaライブラリです。C++ には現在の標準にラムダ関数がないため、これは興味深いものです。
もう 1 つの例はLokiで、「C++ テンプレート メタプログラミングを広範に使用し、一般的に使用されるいくつかのツールを実装しています。タイプリスト、ファンクター、シングルトン、スマート ポインター、オブジェクト ファクトリ、ビジター、マルチメソッドです。」【ウィキペディア】
隠された機能はありませんが、言語 C++ は非常に強力であり、多くの場合、標準の開発者でさえ C++ が何に使用できるか想像できませんでした。
実際、十分に単純な言語構造から、非常に強力なものを書くことができます。そのようなものの多くは、例として www.boost.org で入手できます (およびその中のhttp://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html )。
単純な言語構成を強力なものに組み合わせる方法を理解するには、David Vandevoorde、Nicolai M. Josuttis による「C++ Templates: The Complete Guide」と、Andrei Alexandrescu による本当に魔法の本「Modern C++ Design ...」を読むとよいでしょう。 .
そして最後に、C++ を学ぶのは難しいので、それを埋めてみるべきです ;)
名前のない名前空間について知っている人はほとんどいないようです。
namespace {
// Classes, functions, and objects here.
}
名前のない名前空間は、次のものに置き換えられたかのように動作します。
namespace __unique_name__ { /* empty body */ }
using namespace __unique_name__;
namespace __unique_name__ {
// original namespace body
}
「..翻訳単位内の[この一意の名前]のすべての出現は同じ識別子に置き換えられ、この識別子はプログラム全体の他のすべての識別子とは異なります。」[C++03、7.3.1.1/1]
ほとんどのC++開発者は、テンプレートメタプログラミングの力を無視しています。LokiLibaryをチェックしてください。タイプリスト、ファンクター、シングルトン、スマートポインター、オブジェクトファクトリ、ビジター、およびテンプレートメタプログラミングを広範囲に使用するマルチメソッド(wikipediaから)などのいくつかの高度なツールを実装します。ほとんどの場合、これらは「隠された」c++機能と見なすことができます。
C++ の真実から。
同じスコープ内で同一のシグネチャを持つ関数を定義するため、これは正当です。
template<class T> // (a) a base template
void f(T) {
std::cout << "f(T)\n";
}
template<>
void f<>(int*) { // (b) an explicit specialization
std::cout << "f(int *) specilization\n";
}
template<class T> // (c) another, overloads (a)
void f(T*) {
std::cout << "f(T *)\n";
}
template<>
void f<>(int*) { // (d) another identical explicit specialization
std::cout << "f(int *) another specilization\n";
}
- クラス メソッドへのポインタ
- 「typename」キーワード
「未定義の動作」がたくさんあります。彼らが良い本を読んだり、標準を読んだりするのを避ける方法を学ぶことができます。
フリー関数ポインターとメンバー関数ポインターの初期化の違いに注意してください。
メンバー関数:
struct S
{
void func(){};
};
int main(){
void (S::*pmf)()=&S::func;// & is mandatory
}
と無料機能:
void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well;
}
この冗長な&のおかげで、ストリームマニピュレータ(無料の関数)をチェーンに追加できます。
cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
map::insert(std::pair(key, value));
キー値が既に存在する場合は上書きしません。定義の直後にクラスをインスタンス化できます: (この機能により、セミコロンがないために何百ものコンパイル エラーが発生したことを付け加えるかもしれません。クラスでこれを使用する人を見たことがありません)。
class MyClass {public: /* code */} myClass;
main() は戻り値を必要としません:
int main(){}
最短の有効な C++ プログラムです。
スマートポインタクラスを設計していると仮定します。演算子*および->のオーバーロードに加えて、スマートポインタークラスは通常、ブール値への変換演算子を定義します。
template <class T>
class Ptr
{
public:
operator bool() const
{
return (rawptr ? true: false);
}
//..more stuff
private:
T * rawptr;
};
boolへの変換により、クライアントはboolオペランドを必要とする式でスマートポインターを使用できるようになります。
Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
cout<<"int value is: "<<*ptr <<endl;
else
cout<<"empty"<<endl;
さらに、boolへの暗黙の変換は、次のような条件付き宣言で必要です。
if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
//we get here only of px isn't empty
}
残念ながら、この自動変換は、歓迎されない驚きへの門を開きます。
Ptr <int> p1;
Ptr <double> p2;
//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl;
//prints 0, 1, or 2, although there isn't an overloaded operator+()
Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated
//surprise #2
if(pf==pq) //compares bool values, not pointers!
解決策:ポインターからデータメンバー[pMember]への変換によってboolに変換する「間接変換」イディオムを使用して、暗黙の変換が1つだけになるようにします。これにより、前述の予期しない動作が防止されます。pMember->boolではなくbool->somethingそうしないと。
operator delete() が *void に加えて size 引数を取る場合、それは非常に基本クラスになることを意味します。そのサイズ引数は、正しい型を破棄するために型のサイズをチェックすることを可能にします。ここで、スティーブン・デューハーストがこれについて語っています:
通常の 1 引数バージョンではなく、2 引数バージョンの演算子 delete を使用していることにも注意してください。この 2 つの引数のバージョンは、メンバー演算子 delete の別の「通常の」バージョンであり、派生クラスが演算子 delete 実装を継承することを期待する基本クラスでよく使用されます。2 番目の引数には、削除されるオブジェクトのサイズが含まれます。この情報は、カスタム メモリ管理の実装に役立つことがよくあります。
クラスと構造体のクラスキーはほぼ同じです。主な違いは、クラスはデフォルトでメンバーとベースのプライベート アクセスになるのに対し、構造体はデフォルトでパブリックになることです。
// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };
// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };
// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B
共用体もメンバーとメソッドを持つことができ、構造体と同様にデフォルトでパブリック アクセスになります。
C++ には「トリッキーな」構造がたくさんあります。それらは、仮想継承を使用して、 sealed/final クラスの「単純な」実装から移行します。また、Boost のMPL (チュートリアル)など、かなり「複雑な」メタ プログラミング構造に到達します。自分自身を撃つ可能性は無限にありますが、チェックしておくと (つまり、経験豊富なプログラマー)、保守性とパフォーマンスの点で最高の柔軟性を提供できます。
再帰的なテンプレートの挿入はかなりクールだと思います:
template<class int>
class foo;
template
class foo<0> {
int* get<0>() { return array; }
int* array;
};
template<class int>
class foo<i> : public foo<i-1> {
int* get<i>() { return array + 1; }
};
これを使用して、配列のさまざまな部分にポインターを返す 10 ~ 15 個の関数を含むクラスを生成しました。これは、使用した API が値ごとに 1 つの関数ポインターを必要としたためです。
つまり、再帰を介して一連の関数を生成するようにコンパイラをプログラミングします。やさしい。:)
メンバーポインタとメンバーポインタ演算子->*
#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
A* a = new A();
a->d = 8;
printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
return 0;
}
メソッドの場合(a-> *&A :: e)()は、javascriptのFunction.call()に少し似ています
var f = A.e
f.call(a)
メンバーにとっては、[]演算子でアクセスするのと少し似ています
a['d']
一部のコンパイラでは、コマンド ライン スイッチを使用してすべての定義済みマクロを表示できます。これは gcc および icc (Intel の C++ コンパイラ) で動作します。
$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt
MSVC の場合、これらは1 か所にリストされています。他のものについても 1 か所に文書化できますが、上記のコマンドを使用すると、他のすべてのコマンド ライン スイッチを適用した後で、定義されているものと定義されていないもの、および使用されている値を正確に確認できます。
比較 (ソート後):
$ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt
テンプレートへの制約の追加。
私のお気に入り (当分の間) は、A=B=C のようなステートメントに意味論がないことです。A の値は基本的に未定です。
これについて考えてください:
class clC
{
public:
clC& operator=(const clC& other)
{
//do some assignment stuff
return copy(other);
}
virtual clC& copy(const clC& other);
}
class clB : public clC
{
public:
clB() : m_copy()
{
}
clC& copy(const clC& other)
{
return m_copy;
}
private:
class clInnerB : public clC
{
}
clInnerB m_copy;
}
現在、A は clB 型のオブジェクト以外にはアクセスできない型であり、C とは無関係の値を持っている可能性があります。
class Empty {};
namespace std {
// #1 specializing from std namespace is okay under certain circumstances
template<>
void swap<Empty>(Empty&, Empty&) {}
}
/* #2 The following function has no arguments.
There is no 'unknown argument list' as we do
in C.
*/
void my_function() {
cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
// and this doesn't affect things outside of that scope
}
int main() {
using namespace std; /* #4 you can use using in function scopes */
cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
/* #6 falling off of main without an explicit return means "return 0;" */
}
静的キャストを使用した再解釈キャストのエミュレート:
int var;
string *str = reinterpret_cast<string*>(&var);
上記のコードは次と同等です。
int var;
string *str = static_cast<string*>(static_cast<void*>(&var));
テンプレートメタプログラミングはです。
ポインター演算。
これは実際には C の機能ですが、C/C++ を使用していて、その存在に気付いている人はほとんどいないことに気付きました。C 言語のこの機能は、まさにその発明者の天才とビジョンを示していると思います。
簡単に言うと、ポインター演算により、コンパイラーは任意のタイプの a に対して a[n] を *(a+n) として実行できます。補足として、「+」は交換可能であるため、a[n] はもちろん n[a] と同等です。
関数の一部として変数参照を返すことができます。主に恐ろしいコードを生成するために、いくつかの用途があります。
int s ;
vector <int> a ;
vector <int> b ;
int &G(int h)
{
if ( h < a.size() ) return a[h] ;
if ( h - a.size() < b.size() ) return b[ h - a.size() ] ;
return s ;
}
int main()
{
a = vector <int> (100) ;
b = vector <int> (100) ;
G( 20) = 40 ; //a[20] becomes 40
G(120) = 40 ; //b[20] becomes 40
G(424) = 40 ; //s becomes 40
}
実際には隠された機能ではありませんが、純粋な素晴らしさ:
#define private public
たった 1 つのメソッドでゲッターとセッターを同時に定義する人を知っています。このような:
class foo
{
int x;
int* GetX(){
return &x;
}
}
これを通常どおり getter として使用できるようになりました (ほぼ):
int a = *GetX();
そしてセッターとして:
*GetX() = 17;