STL でベクトルについて最初に学んだことを覚えています。しばらくして、自分のプロジェクトの 1 つで bool のベクトルを使用したいと思いました。いくつかの奇妙な動作を見て、いくつかの調査を行った後、 bools のベクトルは実際には bools のベクトルではないことがわかりました。
C++ で避けるべき一般的な落とし穴は他にありますか?
STL でベクトルについて最初に学んだことを覚えています。しばらくして、自分のプロジェクトの 1 つで bool のベクトルを使用したいと思いました。いくつかの奇妙な動作を見て、いくつかの調査を行った後、 bools のベクトルは実際には bools のベクトルではないことがわかりました。
C++ で避けるべき一般的な落とし穴は他にありますか?
短いリストは次のとおりです。
RAII、共有ポインター、および最小限のコーディングは、もちろん C++ に固有のものではありませんが、言語での開発時に頻繁に発生する問題を回避するのに役立ちます。
このテーマに関するいくつかの優れた本は次のとおりです。
これらの本を読むことは、あなたが尋ねているような落とし穴を避けるのに何よりも役立ちました.
まず、受賞歴のあるC++FAQにアクセスする必要があります。落とし穴に対する多くの良い答えがあります。さらに質問がある場合は、IRC##c++
にアクセスしてください。可能であれば、喜んでお手伝いさせていただきます。以下のすべての落とし穴は元々書かれていることに注意してください。それらはランダムなソースからコピーされただけではありません。irc.freenode.org
delete[]
オンnew
、delete
オンnew[]
解決策:上記を実行すると、未定義の動作が発生します。すべてが発生する可能性があります。あなたのコードとそれが何をするのか、そして常にdelete[]
あなたが何をしているのかnew[]
、そしてあなたが何をしているのかを理解delete
すればnew
、それは起こりません。
例外:
typedef T type[N]; T * pT = new type; delete[] pT;
アレイを新しくしたので、あなたはそうする必要がdelete[]
あります。new
したがって、で作業している場合はtypedef
、特に注意してください。
コンストラクタまたはデストラクタで仮想関数を呼び出す
解決策:仮想関数を呼び出しても、派生クラスのオーバーライド関数は呼び出されません。コンストラクターまたは記述子で純粋仮想関数を呼び出すことは、未定義の動作です。
呼び出し中
delete
またはdelete[]
すでに削除されたポインタ
解決策:削除するすべてのポインターに0を割り当てます。呼び出しdelete
またはdelete[]
nullポインターで、何もしません。
'配列'の要素数を計算するときに、ポインタのサイズを取得します。
解決策:配列をポインターとして関数に渡す必要がある場合は、ポインターの横に要素の数を渡します。実際に配列であると想定される配列のsizeofを取得する場合は、ここで提案されている関数を使用してください。
配列をポインタのように使用します。したがって、
T **
2次元配列に使用します。
解決策:それらが異なる理由とそれらの処理方法については、ここを参照してください。
文字列リテラルへの書き込み:
char * c = "hello"; *c = 'B';
解決策:文字列リテラルのデータから初期化される配列を割り当ててから、次のように書き込むことができます。
char c[] = "hello"; *c = 'B';
文字列リテラルへの書き込みは未定義の動作です。とにかく、文字列リテラルからへの上記の変換char *
は非推奨です。したがって、警告レベルを上げると、コンパイラはおそらく警告を発します。
リソースを作成し、何かがスローされたときにそれらを解放するのを忘れます。
解決策:他の回答で指摘されているようなstd::unique_ptr
、または指摘されているスマートポインターを使用します。std::shared_ptr
この例のようにオブジェクトを2回変更します。
i = ++i;
解決策:上記はi
の値に割り当てることになっていますi+1
。しかし、それが何をするかは定義されていません。結果をインクリメントi
して割り当てる代わりi
に、右側でも変更されます。2つのシーケンスポイント間でオブジェクトを変更することは、未定義の動作です。シーケンスポイントには、、、、、および(網羅的ではないリスト!)||
が&&
含まcomma-operator
れます。コードを次のように変更して、正しく動作するようにします。semicolon
entering a function
i = i + 1;
のようなブロッキング関数を呼び出す前にストリームをフラッシュするのを忘れています
sleep
。
解決策std::endl
:の代わりにストリーミングするか\n
、を呼び出すことによってストリームをフラッシュしますstream.flush();
。
変数の代わりに関数を宣言します。
解決策:この問題は、コンパイラがたとえば解釈するために発生します
Type t(other_type(value));
と呼ばれる型のパラメータをt
返し、持つ関数の関数宣言として。最初の引数を括弧で囲むことで解決します。これで、次のタイプの変数を取得できます。Type
other_type
value
t
Type
Type t((other_type(value)));
.cpp
現在の変換単位(ファイル)でのみ宣言されているフリーオブジェクトの関数を呼び出します。
解決策:この標準では、異なる変換単位にわたって定義された(名前空間スコープでの)フリーオブジェクトの作成順序は定義されていません。まだ構築されていないオブジェクトでメンバー関数を呼び出すことは、未定義の動作です。代わりに、オブジェクトの変換ユニットで次の関数を定義し、他の関数から呼び出すことができます。
House & getTheHouse() { static House h; return h; }
これにより、オブジェクトがオンデマンドで作成され、関数を呼び出すときに完全に構築されたオブジェクトが残ります。
.cpp
別のファイルで使用されているときに、ファイルでテンプレートを定義し.cpp
ます。
解決策:ほとんどの場合、のようなエラーが発生しますundefined reference to ...
。すべてのテンプレート定義をヘッダーに入れて、コンパイラーがそれらを使用しているときに、必要なコードを既に生成できるようにします。
static_cast<Derived*>(base);
baseがの仮想基本クラスへのポインタである場合Derived
。
解決策:仮想基本クラスは、継承ツリーで間接的に異なるクラスによって複数回継承されている場合でも、1回だけ発生する基本です。上記を行うことは、規格では許可されていません。dynamic_castを使用してこれを行い、基本クラスがポリモーフィックであることを確認します。
dynamic_cast<Derived*>(ptr_to_base);
ベースが非多型の場合
解決策:標準では、渡されたオブジェクトがポリモーフィックでない場合、ポインターまたは参照のダウンキャストは許可されていません。それまたはその基本クラスの1つは、仮想関数を持っている必要があります。
関数を受け入れるようにする
T const **
解決策:それを使用するよりも安全だと思うかもしれませんT **
が、実際には合格したい人に頭痛の種を引き起こしますT**
:標準では許可されていません。それはなぜそれが許可されないのかについてのきちんとした例を与えます:
int main() {
char const c = ’c’;
char* pc;
char const** pcc = &pc; //1: not allowed
*pcc = &c;
*pc = ’C’; //2: modifies a const object
}
代わりに常に受け入れT const* const*;
ます。
C ++に関するもう1つの(クローズド)落とし穴スレッドは、それらを探している人が見つけることができるように、StackOverflowの質問C++の落とし穴です。
一般的な C++ の落とし穴を回避するのに役立つ C++ の本が必要な人もいます。
効果的なSTLの本は、ブールの問題のベクトルを説明しています:)
Brian はすばらしいリストを持っています。
Scott Wheeler によるWeb ページC++ Pitfalls では、主な C++ の落とし穴のいくつかを取り上げています。
具体的なヒントではありませんが、一般的なガイドライン: ソースを確認してください。C++ は古い言語であり、長年にわたって多くの変化を遂げてきました。それに伴い、ベスト プラクティスも変更されましたが、残念ながら、まだ多くの古い情報が残っています。ここには非常に優れた本がいくつかあります。Scott Meyers の C++ 本はどれも 2 番目に購入できます。Boost と、Boost で使用されるコーディング スタイルに慣れてください。そのプロジェクトに関係する人々は、C++ 設計の最先端にいます。
車輪を再発明しないでください。STL と Boost に精通し、可能な限りそれらの機能を使用して独自の機能を開発してください。特に、非常に正当な理由がない限り、STL 文字列とコレクションを使用してください。auto_ptr と Boost スマート ポインター ライブラリをよく理解し、各タイプのスマート ポインターがどのような状況で使用されることを意図しているかを理解してから、他の方法では生のポインターを使用していた可能性があるあらゆる場所でスマート ポインターを使用します。あなたのコードは同じように効率的で、メモリ リークが発生しにくくなります。
C スタイルのキャストの代わりに、static_cast、dynamic_cast、const_cast、および reinterpret_cast を使用します。C スタイルのキャストとは異なり、要求していると思っているのとは異なるタイプのキャストを本当に要求しているかどうかを知らせてくれます。そしてそれらは視覚的に目立ち、キャストが行われていることを読者に警告します.
C のように C++ を使用する。コードに作成とリリースのサイクルがある。
C++ では、これは例外セーフではないため、リリースが実行されない場合があります。C++ では、RAIIを使用してこの問題を解決します。
手動で作成および解放するすべてのリソースは、オブジェクトでラップする必要があるため、これらのアクションはコンストラクター/デストラクターで実行されます。
// C Code
void myFunc()
{
Plop* plop = createMyPlopResource();
// Use the plop
releaseMyPlopResource(plop);
}
C++ では、これをオブジェクトにラップする必要があります。
// C++
class PlopResource
{
public:
PlopResource()
{
mPlop=createMyPlopResource();
// handle exceptions and errors.
}
~PlopResource()
{
releaseMyPlopResource(mPlop);
}
private:
Plop* mPlop;
};
void myFunc()
{
PlopResource plop;
// Use the plop
// Exception safe release on exit.
}
難しい方法で学ばなければよかったと思う 2 つの落とし穴:
(1) 多くの出力 (printf など) はデフォルトでバッファリングされます。クラッシュするコードをデバッグしていて、バッファリングされた debug ステートメントを使用している場合、最後に表示される出力は、実際にはコード内で最後に検出された print ステートメントではない可能性があります。解決策は、各デバッグ出力の後にバッファをフラッシュする (またはバッファリングを完全にオフにする) ことです。
(2) 初期化には注意してください - (a) クラスインスタンスをグローバル / スタティックとして避ける。(b) ポインターの NULL などの些細な値であっても、すべてのメンバー変数を ctor 内の安全な値に初期化しようとします。
理由: グローバル オブジェクトの初期化の順序は保証されていません (globals には静的変数が含まれます)。そのため、オブジェクト Y の前にオブジェクト X が初期化されることに依存しているため、非決定論的に失敗したように見えるコードになる可能性があります。クラスのメンバー bool や enum などのプリミティブ型の変数を使用すると、予期しない状況で異なる値になることがあります。これもまた、動作が非常に非決定的に見える場合があります。
すでに何度か言及しましたが、Scott Meyers の本「Effective C++ 」と「 Effective STL」は、C++ を支援するための金の価値があります。
考えてみれば、Steven Dewhurst のC++ Gotchasも優れた「塹壕から」のリソースです。独自の例外のローリングとその構築方法に関する彼の項目は、あるプロジェクトで私を本当に助けてくれました。
ここに私が陥る不幸があったいくつかの穴があります. これらすべてには、私を驚かせた行動に噛まれて初めて理解した正当な理由があります.
virtual
コンストラクターの関数はそうではありません。
ODR (One Definition Rule)に違反しないでください。それが匿名名前空間の目的です (とりわけ)。
メンバーの初期化の順序は、宣言された順序によって異なります。
class bar {
vector<int> vec_;
unsigned size_; // Note size_ declared *after* vec_
public:
bar(unsigned size)
: size_(size)
, vec_(size_) // size_ is uninitialized
{}
};
デフォルト値であり、virtual
セマンティクスが異なります。
class base {
public:
virtual foo(int i = 42) { cout << "base " << i; }
};
class derived : public base {
public:
virtual foo(int i = 12) { cout << "derived "<< i; }
};
derived d;
base& b = d;
b.foo(); // Outputs `derived 42`
本C++ Gotchasが役に立つかもしれません。
初心者の開発者にとって最も重要な落とし穴は、C と C++ の混同を避けることです。C++ は、単に優れた C やクラスを備えた C として扱われるべきではありません。これは、その能力を削ぎ落とし、危険にさらす可能性があるためです (特に C のようにメモリを使用する場合)。
PRQA には、 Scott Meyers、Bjarne Stroustrop、および Herb Sutter の書籍に基づいた優れた無料の C++ コーディング標準があります。このすべての情報を 1 つのドキュメントにまとめます。
boost.orgをチェックしてください。多くの追加機能、特にスマート ポインターの実装を提供します。
疑似クラスや準クラスは避けてください...基本的にオーバーデザイン。
スマート ポインターとコンテナー クラスを使用する場合は注意してください。
基本クラスのデストラクタ virtual を定義するのを忘れています。これはdelete
、 Base* を呼び出しても派生部分が破棄されないことを意味します。
名前空間をまっすぐに保ちます(構造体、クラス、名前空間、および使用法を含む)。これは、プログラムがコンパイルされないときの私の一番のフラストレーションです。
混乱させるには、ストレートポインタをよく使用します。代わりに、ほとんどすべてにRAIIを使用します。もちろん、適切なスマートポインターを使用するようにしてください。ハンドルまたはポインタ型クラスの外のどこかに「delete」と書くと、間違っている可能性が非常に高くなります。
static_cast
仮想基本クラスのダウンキャスト
そうではありません...私の誤解について:実際A
にはそうではないのに、以下は仮想基本クラスだと思いました。これは、10.3.1 によると、ポリモーフィック クラスです。ここを使えstatic_cast
ば大丈夫そうです。
struct B { virtual ~B() {} };
struct D : B { };
要約すると、はい、これは危険な落とし穴です。
「 C++ の落とし穴: コーディングと設計における一般的な問題の回避」という本を読んでください。
ブリズパスタ。よく見かける大物です…
初期化されていない変数は、私の学生が犯す大きな間違いです。多くの Java 関係者は、「int カウンター」と言うだけではカウンターが 0 に設定されないことを忘れています。h ファイルで変数を定義する必要があるため (そしてオブジェクトのコンストラクター/セットアップで変数を初期化する必要があるため)、忘れがちです。
for
ループ/配列アクセスでの off-by-one エラー。
voodoo の起動時にオブジェクト コードが適切にクリーニングされない。
エッセイ/記事のポインタ、リファレンス、値は非常に便利です。落とし穴やグッドプラクティスを回避することは避けてください。主にC++のプログラミングのヒントを含むサイト全体を閲覧することもできます。
ポインターを逆参照する前に、必ずポインターを確認してください。Cでは、通常、不良ポインターを間接参照するポイントでのクラッシュを期待できます。C ++では、問題の原因から遠く離れた場所でクラッシュする無効な参照を作成できます。
class SomeClass
{
...
void DoSomething()
{
++counter; // crash here!
}
int counter;
};
void Foo(SomeClass & ref)
{
...
ref.DoSomething(); // if DoSomething is virtual, you might crash here
...
}
void Bar(SomeClass * ptr)
{
Foo(*ptr); // if ptr is NULL, you have created an invalid reference
// which probably WILL NOT crash here
}
を忘れて&
、参照の代わりにコピーを作成する。
これは、さまざまな方法で2回発生しました。
1 つのインスタンスが引数リストに含まれていたため、大きなオブジェクトがスタックに置かれ、スタック オーバーフローと組み込みシステムのクラッシュが発生しました。
&
オブジェクトがコピーされたという効果で、インスタンス変数の を忘れました。コピーのリスナーとして登録した後、なぜ元のオブジェクトからコールバックを取得できなかったのか疑問に思いました。
違いが小さくてわかりにくいため、どちらもかなり見つけにくい場所です。それ以外の場合、オブジェクトと参照は構文的に同じように使用されます。
私は長年 C++ の開発に携わってきました。何年も前に私が抱えていた問題の簡単な要約を書きました。標準に準拠したコンパイラはもはや実際には問題ではありませんが、概要を説明した他の落とし穴がまだ有効であると思われます。
意図は(x == 10)
:
if (x = 10) {
//Do something
}
自分では絶対に間違えないと思っていたのですが、最近やってしまいました。
#include <boost/shared_ptr.hpp>
class A {
public:
void nuke() {
boost::shared_ptr<A> (this);
}
};
int main(int argc, char** argv) {
A a;
a.nuke();
return(0);
}