一連の質問の「隠された機能」に関しては、C ++の愛はありませんか?私はそれをそこに捨てるだろうと考えました。C ++の隠された機能は何ですか?
一連の質問の「隠された機能」に関しては、C ++の愛はありませんか?私はそれをそこに捨てるだろうと考えました。C ++の隠された機能は何ですか?
回答:
ほとんどのC ++プログラマーは、3項演算子に精通しています。
x = (y < 0) ? 10 : 20;
ただし、左辺値として使用できることを理解していません。
(a == 0 ? a : b) = 1;
これは略記です
if (a == 0)
a = 1;
else
b = 1;
注意して使用してください:-)
(value ? function1 : function2)()
。
function1
とfunction2
implictly関数ポインタに変換され、結果が暗黙的に変換されたバックです。
エラーなしでURIをC ++ソースに入れることができます。例えば:
void foo() {
http://stackoverflow.com/
int bar = 4;
...
}
goto
C ++にはあるで使用するため)。2つのスラッシュに続くものはすべてコメントです。したがって、とはhttp://stackoverflow.com
、http
(あなたは理論的に書くことができるラベルであるgoto http;
)、そして//stackoverflow.com
ちょうど行末コメントです。これらは両方とも正当なC ++であるため、構造はコンパイルされます。もちろん、それは漠然と有用なことは何もしません。
goto http;
実際にはURLをフォローしていません。:(
私はそこのほとんどの投稿に同意します。C++はマルチパラダイム言語であるため、見つけることができる「隠された」機能(「未定義の動作」以外は避けなければならない)は、機能の巧妙な使用法です。
これらの機能のほとんどは、言語の組み込み機能ではなく、ライブラリベースの機能です。
最も重要なのはRAIIであり、Cの世界から来たC ++開発者によって何年もの間無視されてきました。オペレーターの過負荷は、配列のような動作(添字演算子)、ポインターのような演算(スマートポインター)、組み込みのような演算(行列の乗算)の両方を可能にする誤解されている機能であることがよくあります。
例外の使用はしばしば困難ですが、いくつかの作業により、例外の安全性を通じて非常に堅牢なコードを生成できます仕様(失敗しない、または成功する、または戻るようなコミットのような機能を持つコードを含む)ますその元の状態)。
C ++の「隠された」機能で最も有名なのは、テンプレートのメタプログラミングです。です。これにより、ランタイムではなくコンパイル時にプログラムを部分的(または完全に)実行できます。ただし、これは難しい作業であり、試す前にテンプレートをしっかりと把握しておく必要があります。
他にも、複数のパラダイムを利用して、C ++の祖先、つまりCの外部に「プログラミングの方法」を作成します。
ファンクタを使用することで、型安全性が追加され、ステートフルになる関数をシミュレートできます。コマンドパターンを使用すると、コードの実行を遅らせることができます。他のほとんどの設計パターンは、C ++に簡単かつ効率的に実装して、「公式のC ++パラダイム」のリストに含まれていないはずの代替コーディングスタイルを生成できます。
テンプレートを使用することで、最初は考えていなかったタイプを含め、ほとんどのタイプで機能するコードを作成できます。タイプセーフを増やすこともできます(自動化されたタイプセーフなmalloc / realloc / freeなど)。C ++オブジェクト機能は非常に強力です(したがって、不注意に使用すると危険です)が、動的なポリモーフィズムでさえ、C ++の静的バージョンであるCRTPがあります。ます。
スコットマイヤーズの「Effective C ++」タイプの本またはHerb Sutterの「Exceptional C ++」タイプの本のほとんどは読みやすく、C ++の既知の機能とあまり知られていない機能に関する情報の宝物であることがわかりました。
私が好むのは、あらゆるJavaプログラマーの頭を恐ろしくさせるものです。C++では、オブジェクトに機能を追加するための最もオブジェクト指向の方法は、メンバーではなくメンバー以外の非フレンド関数を使用する方法です。関数(つまり、クラスメソッド)。
C ++では、クラスのインターフェイスは、同じ名前空間内のそのメンバー関数と非メンバー関数の両方です
フレンド以外の非メンバー関数には、内部クラスへの特権アクセスがありません。そのため、非メンバーで非フレンドのメンバー関数を使用すると、クラスのカプセル化が弱まります。
これは、経験豊富な開発者でさえ驚くことはありません。
(出典:とりわけ、ハーブサッターのオンライングルオブザウィーク#84:http : //www.gotw.ca/gotw/084.htm)
学校でずっと聞いたことがなかったために、少し隠されていると思われる言語機能の1つは、名前空間のエイリアスです。ブーストのドキュメントでその例に出くわすまで、それは私の注意を引くことはありませんでした。もちろん、私はそれについて知っているので、標準のC ++リファレンスで見つけることができます。
namespace fs = boost::filesystem;
fs::path myPath( strPath, fs::native );
using
。
変数は、for
ループのinit部分で宣言できるだけでなく、クラスや関数でも宣言できます。
for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
...
}
これにより、異なるタイプの複数の変数が可能になります。
配列演算子は結合的です。
A [8]は*(A + 8)の同義語です。加算は結合的であるため、*(8 + A)と書き換えることができます。これは、..... 8 [A]の同義語です。
役に立たなかった... :-)
A
はまったく関係ありません。たとえば、A
でしたchar*
、コードはまだ有効でしょう。
あまり知られていないことの1つは、共用体もテンプレートになる可能性があることです。
template<typename From, typename To>
union union_cast {
From from;
To to;
union_cast(From from)
:from(from) { }
To getTo() const { return to; }
};
また、コンストラクターやメンバー関数を持つこともできます。継承とは関係ありません(仮想関数を含む)。
From
、未定義の動作を呼び出しTo
ます。このような共用体は、定義された動作で使用できます(To
unsigned charの配列、またはと初期シーケンスを共有する構造体From
)。未定義の方法で使用しても、低レベルの作業には役立つ場合があります。とにかく、これはユニオンテンプレートの1つの例にすぎません。テンプレート化されたユニオンには他の用途があるかもしれません。
C ++は標準です。隠し機能はありません...
C ++はマルチパラダイム言語であり、隠された機能があることに最後のお金を賭けることができます。多くの1つの例:テンプレートメタプログラミング。標準委員会の誰も、コンパイル時に実行されるチューリング完全なサブ言語が存在することを意図していませんでした。
Cで機能しないもう1つの隠された機能は、単項の機能です。 +
子の。あなたはそれを使ってあらゆる種類のものを促進し腐敗させることができます
+AnEnumeratorValue
また、以前は列挙型であった列挙子の値は、その値に適合することができる完全な整数型になりました。手動では、そのタイプはほとんどわかりません。これは、たとえば、列挙にオーバーロードされた演算子を実装する場合に必要です。
クラス外の定義なしでクラス内の静的初期化子を使用するクラスを使用する必要がありますが、リンクに失敗することがありますか?オペレーターは、そのタイプにassumptinまたは依存関係を作成せずに一時ファイルを作成するのに役立ちます
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;
}
主な使用法は、例外を他の例外クラスに変換して再スローするか、例外と戻り値ベースのエラーコード処理の間で変換することです。
return
Function Tryのcatchブロックからはできません。再スローのみです。
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; };
template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;
pointer<function<void,int>> f(pointer<function<void,void>>);
pointer<void(int)> f(pointer<void()>);
function<pointer<function<void,int>>,pointer<function<void,void>>> f;
非常に隠された機能は、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条件でも)。しかし、私はこれらがそれほど有用であるかどうかあまりわかりません:)
if((a = f()) == b) ...
、この答えは実際には条件内の変数を宣言しています。
for(...; int i = foo(); ) ...;
これi
がtrueである限りボディを通過し、毎回初期化されるようです。表示するループは、変数宣言を示しているだけで、同時に条件として機能する変数宣言は示していません:)
場合によってはカンマ演算子を有効に使用しますが、ユーザー定義のカンマ演算子が邪魔にならないようにしたい場合があります。たとえば、左側と右側の間のシーケンスポイントに依存したり、必要なものに干渉しないようにしたい場合などです。アクション。これが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);
}
確かに、あなたが見るように、これはあまりにも多くの損傷を引き起こすでしょう。しかし今、メンバーポインターはこの保護を回避することを可能にします!重要な点は、メンバーポインターの型が、アドレスを取得するときに指定したクラスではなく、実際にそのメンバーを含むクラスにバインドされていることです。これにより、チェックを回避できます
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);
}
もう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
}
これらは「代理呼び出し機能」と呼ばれます。
非表示の機能:
関数がその例外仕様にリストされていない例外をスローしたが、関数が 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)
}
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 ++プログラマーの数に驚かされます。
.find()
。知らない人は気が狂うことがあります。
const map::operator[]
エラーメッセージを生成する」
名前のない名前空間に関数または変数を配置すると、を使用static
してファイルスコープに制限することが非推奨になります。
static
、グローバルスコープでは決して非推奨ではありません。(参考:C ++ 03§D.2)
static
useは、クラス型または関数内でのみ使用する必要があります。
クラステンプレートで通常のフレンド関数を定義するには、特別な注意が必要です。
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
関数は値を返すことはできませんが、non-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));
vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());
-第二のparam後に行方不明の括弧
ビットフィールドをテンプレート化できます。
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));
3番目と5番目を除くすべてがSomeType
スタック上のオブジェクトを定義し、それを初期化します(u
最初の2つの場合と4番目のデフォルトコンストラクターを使用)。3番目はパラメーターをとらずにを返す関数を宣言していますSomeType
。5番目は同様に宣言しています型の値によって一つのパラメータを取る関数SomeType
の名前u
。
前方宣言を取り除く:
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;
FStruct s = {};
さらに短いです。
main
か?私がお勧めしたいglobal().main();
とちょうど(シングルトンを忘れて、あなたはそれの寿命が延長され得る、一時的とだけ仕事をすることができます)
三項条件演算子で?:
は、その2番目と3番目のオペランドに「適切な」タイプ(非公式に話す)が必要です。ただし、この要件には1つの例外があります(しゃれが意図されています)。2番目または3番目のオペランドvoid
は、他のオペランドの型に関係なく、(typeを持つ)スロー式にすることができます。
つまり、?:
演算子を使用して、次の正しく有効なC ++式を記述できます。
i = a > b ? a : throw something();
ところで、throw式が実際には(型のvoid
)式であり、ステートメントではないという事実は、C ++言語のもう1つのあまり知られていない機能です。これは、とりわけ、次のコードが完全に有効であることを意味します
void foo()
{
return throw something();
}
この方法で行うことにはあまり意味がありません(おそらく、いくつかの一般的なテンプレートコードでは、これが便利になる場合があります)。
支配ルールは便利ですが、ほとんど知られていません。これは、基本クラスラティスを通る一意でないパス内であっても、メンバーが仮想基本クラスに属している場合、部分的に非表示のメンバーの名前検索は一意であると述べています。
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 C
例に含めた特別な理由...?乾杯。