「隠された機能」の一連の質問に関しては、C++は好きではありませんか?私はそこにそれを投げると思いました。 C++の隠された機能にはどのようなものがありますか?
ほとんどの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++開発者によって何年も無視されることがよくあります。 演算子のオーバーロードは、配列のような動作(添字演算子)、ポインターのような操作(スマートポインター)、およびビルドインのようなものの両方を有効にする誤解された機能であることがよくあります操作(乗算行列。
exceptionの使用はしばしば困難ですが、いくつかの作業を行うことで、exception安全性仕様(失敗しないコード、または成功するか、元の状態に戻るコミットのような機能を持つコードを含む)。
C++の最も有名な「隠された」機能はtemplate metaprogrammingです。これは、コンパイル時にプログラムを部分的に(または完全に)実行できるようにするためです。ランタイムの代わりに。ただし、これは難しく、試してみる前にテンプレートをしっかりと把握する必要があります。
他には、C++の祖先、つまりCの外部で「プログラミングの方法」を生み出すために複数のパラダイムを使用します。
functorsを使用することで、追加の型安全性とステートフルで関数をシミュレートできます。 commandパターンを使用して、コードの実行を遅らせることができます。他のほとんどのデザインパターンをC++で簡単かつ効率的に実装して、「公式C++パラダイム」のリストに含まれないはずの代替コーディングスタイルを作成できます。
templatesを使用することで、最初に考えたものを含め、ほとんどのタイプで動作するコードを生成できます。型の安全性も高めることができます(自動化された型保証されたmalloc/realloc/freeなど)。 C++オブジェクトの機能は非常に強力です(したがって、不注意に使用すると危険です)が、動的ポリモーフィズムでもC++の静的バージョンがあります:[〜#〜] crtp [〜#〜]。
Scott Meyersの「Effective C++」タイプの本、またはHerb Sutterの「Exceptional C++」タイプの本読みやすく、C++の既知の機能とあまり知られていない機能に関する非常に貴重な情報を提供します。
私が好むのは、あらゆるJavaプログラマーが恐怖から立ち上がる)の髪を作るべきものです:C++では、に機能を追加する最もオブジェクト指向の方法オブジェクトは、member-function(つまり、クラスメソッド)ではなく、非メンバー、非フレンド関数を介しています。
C++では、クラスのインターフェイスは、同じ名前空間内のメンバー関数と非メンバー関数の両方です
非友人非メンバー関数には、内部クラスへの特権アクセスがありません。そのため、非メンバーで非フレンドのメンバー関数を使用すると、クラスのカプセル化が弱まります。
これは、経験豊富な開発者であっても驚くことではありません。
(情報源:とりわけ、ハーブサッターのオンラインの第一人者#84: http://www.gotw.ca/gotw/084.htm )
私が学校にいる間ずっと聞いたことがないので、私はいくらか隠されていると考える言語機能の1つは名前空間のエイリアスです。 boostドキュメントでその例に出くわすまで、それは私の注意を引き付けませんでした。もちろん、今ではそれについて知っているので、標準のC++リファレンスで見つけることができます。
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
for
ループのinit部分で変数を宣言できるだけでなく、クラスと関数も宣言できます。
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(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++は、そのクラスが基本クラスである場合でも、クラスのオブジェクトの非静的保護メンバーにアクセスすることを禁止します
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);
}
確かに、あなたが見るように、これはあまりにも多くの損傷を引き起こすでしょう。しかし、今では、メンバーポインターによってこの保護を回避できます!重要な点は、メンバーポインターのタイプが、実際にそのメンバーを含むクラスにバインドされていることです-notを取得するときに指定したクラスに住所。これにより、チェックを回避できます
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);
}
非表示の機能:
関数が例外仕様にリストされていない例外をスローするが、その関数の例外仕様にstd::bad_exception
がある場合、例外はstd::bad_exception
に変換され、自動的にスローされます。そうすれば、少なくともbad_exception
がスローされたことを知ることができます。続きを読む こちら 。
関数tryブロック
クラステンプレートのtypedefを明確にするためのテンプレートキーワード。メンバーテンプレート特化の名前が.
、->
、または::
演算子の後に表示され、その名前に明示的に修飾されたテンプレートパラメーターがある場合、キーワードテンプレートをメンバーテンプレート名の前に付けます。続きを読む こちら 。
関数パラメータのデフォルトは実行時に変更できます。続きを読む こちら 。
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
}
これらは「代理呼び出し関数」と呼ばれます。
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++テンプレート:完全ガイド /セクション8.4からこのセクションを貼り付けました
ほとんど知られていないが、次のコードは問題ありません
void f() { }
void g() { return f(); }
以下の奇妙なものも
void f() { return (void)"i'm discarded"; }
これを知っていれば、いくつかの分野で活用することができます。 1つの例:void
関数は値を返すことができませんが、非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;
};
私はまだこの目的を思いつきませんでしたが、確かに驚きました。
支配規則は有用ですが、ほとんど知られていません。基本クラスラティスを通る一意でないパスにある場合でも、メンバーが仮想基本クラスに属している場合、部分的に隠されたメンバーの名前検索は一意であると言います。
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(); } };
これを使用して implement alignment-support 支配規則によって最も厳密な配置を自動的に計算します。
これは、仮想関数だけでなく、typedef名、静的/非仮想メンバー、その他にも適用されます。メタプログラムに上書き可能な特性を実装します。
三項条件演算子?:
では、2番目と3番目のオペランドが「同意できる」タイプであることが必要です(非公式に言えば)。ただし、この要件には1つの例外があります(しゃれを意図しています)。2番目または3番目のオペランドは、他のオペランドのタイプに関係なく、スロー式(void
タイプ)になります。
言い換えると、?:
演算子を使用して、次の明確に有効なC++式を記述できます。
i = a > b ? a : throw something();
ところで、throw式は実際には式(タイプvoid
)であり、ステートメントではないという事実は、C++言語のもう1つのあまり知られていない機能です。これは、とりわけ、次のコードが完全に有効であることを意味します
void foo()
{
return throw something();
}
ただし、この方法で行うことにはあまり意味がありません(いくつかの汎用テンプレートコードでは、これが便利な場合があります)。
前方宣言を取り除く:
struct global
{
void main()
{
a = 1;
b();
}
int a;
void b(){}
}
singleton;
?:演算子を使用したスイッチステートメントの記述:
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;
このブログは、C++の難解さに関する素晴らしいリソースであることがわかりました。 C++ Truths 。
危険な秘密は
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
}
ローカルクラスは素晴らしいです:
struct MyAwesomeAbstractClass
{ ... };
template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
struct ans : MyAwesomeAbstractClass
{
// Make the implementation depend on T
};
return new ans(...);
}
無駄なクラス定義で名前空間を汚染しないため、非常にきれいです...
GCC開発者 にさえ隠されている1つの隠された機能は、文字列リテラルを使用して配列メンバーを初期化することです。 C配列を操作する必要がある構造があり、デフォルトの内容で配列メンバーを初期化するとします
struct Person {
char name[255];
Person():name("???") { }
};
これは機能し、char配列と文字列リテラル初期化子でのみ機能します。 strcpy
は不要です!
多くの例の1つ:テンプレートのメタプログラミング。標準委員会の誰も、コンパイル時に実行されるチューリング完全なサブ言語の存在を意図していませんでした。
テンプレートのメタプログラミングは、ほとんど隠された機能ではありません。 boostライブラリにもあります。 [〜#〜] mpl [〜#〜] を参照してください。しかし、「ほとんど隠されている」で十分であれば、 boost libraries を見てください。強力なライブラリの支援なしでは簡単にアクセスできない多くのグッズが含まれています。
1つの例は boost.lambda ライブラリです。これは、C++が現在の標準にラムダ関数を持たないため興味深いものです。
別の例は Loki です。これは「C++テンプレートメタプログラミングを広範囲に使用し、一般的に使用されるいくつかのツールを実装します:タイプリスト、ファンクター、シングルトン、スマートポインター、オブジェクトファクトリ、ビジター、マルチメソッド」。 [ Wikipedia ]
隠された機能はありませんが、C++言語は非常に強力であり、標準の開発者でさえC++の用途を想像できませんでした。
実際、シンプルで十分な言語構成から、非常に強力な何かを書くことができます。そのようなものの多くは、例としてwww.boost.orgで入手できます(および http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html の中に) 。
シンプルな言語構成を強力なものに組み合わせる方法を理解するには、 「C++ Templates:The Complete Guide」by David Vandevoorde、Nicolai M. Josuttis および本当に魔法の本 "Modern C++ Design ... "Andrei Alexandrescu による。
そして最後に、C++を習得するのは難しいので、それを埋めてみるべきです;)
名前のない名前空間について知っている人はほとんどいないようです。
namespace {
// Classes, functions, and objects here.
}
名前のない名前空間は、次のように置き換えられたように動作します。
namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
// original namespace body
}
「..ここで、翻訳単位内の[この一意の名前]の出現はすべて同じ識別子に置き換えられ、この識別子はプログラム全体の他のすべての識別子とは異なります。 " [C++ 03、7.3.1.1/1]
多くの「未定義の動作」があります。良い本を読んだり、標準を読んだりするのを避ける方法を学ぶことができます。
ほとんどのC++開発者は、テンプレートメタプログラミングの力を無視します。 Loki Libary をご覧ください。タイプリスト、ファンクター、シングルトン、スマートポインター、オブジェクトファクトリー、ビジター、テンプレートメタプログラミングを広範囲に使用するマルチメソッド( wikipedia )などのいくつかの高度なツールを実装します。ほとんどの場合、これらは「隠された」c ++機能と見なすことができます。
C++ Truths から。
同じスコープ内で同じシグネチャを持つ関数を定義するため、これは正当です:
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";
}
main()は戻り値を必要としません:
int main(){}
は、最短の有効なC++プログラムです。
空き関数ポインターとメンバー関数ポインターの初期化の違いに注意してください。
メンバー関数:
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;
C++には、「トリッキーな」コンストラクトがたくさんあります。これらは、仮想継承を使用する sealed/final classes の「単純な」実装から得られます。また、Boostの [〜#〜] mpl [〜#〜] ( tutorial )のようなかなり「複雑な」メタプログラミング構成体に到達します。自分自身を足で撃つ可能性は無限にありますが、チェックし続けると(つまり、経験豊富なプログラマー)、保守性とパフォーマンスに関して最高の柔軟性が得られます。
クラスと構造体のクラスキーはほぼ同じです。主な違いは、クラスのデフォルトはメンバーとベースのプライベートアクセスであるのに対して、構造体のデフォルトはパブリックであるということです。
// 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
組合はメンバーとメソッドを持つこともでき、構造体と同様にデフォルトでパブリックアクセスになります。
スマートポインタークラスを設計しているとします。演算子*および->のオーバーロードに加えて、スマートポインタークラスは通常、boolへの変換演算子を定義します。
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つだけになるようにします。それ以外。
演算子delete()が* voidに加えてsize引数を取る場合、それは、非常に基本クラスになることを意味します。そのサイズ引数により、正しいサイズを破棄するためにタイプのサイズをチェックすることが可能になります。ここで Stephen Dewhurst がこれについて伝えているもの:
また、通常の1引数バージョンではなく、2引数バージョンの演算子deleteを使用していることにも注意してください。この2つの引数のバージョンは、派生クラスが演算子削除の実装を継承することを期待する基本クラスでよく使用されるメンバー演算子削除の別の「通常の」バージョンです。 2番目の引数には、削除されるオブジェクトのサイズが含まれます。これは、カスタムメモリ管理の実装に役立つ情報です。
再帰的なテンプレートのインスタンス化は非常にクールだと思います。
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; }
};
使用したAPIには値ごとに1つの関数ポインターが必要だったため、配列のさまざまな部分にポインターを返す10〜15個の関数を持つクラスを生成するためにそれを使用しました。
つまりコンパイラーをプログラミングして、再帰を介して一連の関数を生成します。やさしい。 :)
(とりあえず)私のお気に入りは、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とは無関係の値を持つ場合があります。
テンプレートに constraints を追加します。
メンバーポインターとメンバーポインター演算子-> *
#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の場合、それらは 単一の場所 にリストされます。それらは他の人のために単一の場所で文書化することもできますが、上記のコマンドを使用すると、すべてを適用した後、定義されているものと定義されていないもの、および使用されている値を明確に見ることができます他のコマンドラインスイッチ。
比較(ソート後):
$ diff gxx-macros.txt icx-macros.txt
$ diff gxx-macros.txt gcc-macros.txt
$ diff icx-macros.txt icc-macros.txt
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;" */
}