75

STL でベクトルについて最初に学んだことを覚えています。しばらくして、自分のプロジェクトの 1 つで bool のベクトルを使用したいと思いました。いくつかの奇妙な動作を見て、いくつかの調査を行った後、 bools のベクトルは実際には bools のベクトルではないことがわかりました。

C++ で避けるべき一般的な落とし穴は他にありますか?

4

29 に答える 29

76

短いリストは次のとおりです。

  • 共有ポインタを使用してメモリの割り当てとクリーンアップを管理することにより、メモリ リークを回避する
  • Resource Acquisition Is Initialization (RAII) イディオムを使用してリソースのクリーンアップを管理する - 特に例外が存在する場合
  • コンストラクターで仮想関数を呼び出さない
  • 可能な場合は最小限のコーディング手法を採用します。たとえば、必要な場合にのみ変数を宣言する、変数のスコープを設定する、可能な場合は早期に設計するなどです。
  • スローする例外と、間接的に使用する可能性のあるクラスによってスローされる例外の両方に関して、コード内の例外処理を真に理解してください。これは、テンプレートが存在する場合に特に重要です。

RAII、共有ポインター、および最小限のコーディングは、もちろん C++ に固有のものではありませんが、言語での開発時に頻繁に発生する問題を回避するのに役立ちます。

このテーマに関するいくつかの優れた本は次のとおりです。

  • 効果的な C++ - スコット マイヤーズ
  • より効果的な C++ - Scott Meyers
  • C++ コーディング標準 - Sutter & Alexandrescu
  • C++ FAQ - クライン

これらの本を読むことは、あなたが尋ねているような落とし穴を避けるのに何よりも役立ちました.

于 2008-08-27T15:17:15.537 に答える
52

重要度の高い順に落とし穴

まず、受賞歴のあるC++FAQにアクセスする必要があります。落とし穴に対する多くの良い答えがあります。さらに質問がある場合は、IRC##c++にアクセスしてください。可能であれば、喜んでお手伝いさせていただきます。以下のすべての落とし穴は元々書かれていることに注意してください。それらはランダムなソースからコピーされただけではありません。irc.freenode.org


delete[]オンnewdeleteオン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れます。コードを次のように変更して、正しく動作するようにします。semicolonentering a functioni = i + 1;


その他の問題

のようなブロッキング関数を呼び出す前にストリームをフラッシュするのを忘れていますsleep

解決策std::endl:の代わりにストリーミングするか\n、を呼び出すことによってストリームをフラッシュしますstream.flush();


変数の代わりに関数を宣言します。

解決策:この問題は、コンパイラがたとえば解釈するために発生します

Type t(other_type(value));

と呼ばれる型のパラメータをt返し、持つ関数の関数宣言として。最初の引数を括弧で囲むことで解決します。これで、次のタイプの変数を取得できます。Typeother_typevaluetType

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++の落とし穴です。

于 2008-11-15T21:02:15.377 に答える
16

一般的な C++ の落とし穴を回避するのに役立つ C++ の本が必要な人もいます。

効果的な C++
より効果的な C++
効果的な STL

効果的なSTLの本は、ブールの問題のベクトルを説明しています:)

于 2008-08-27T15:15:18.460 に答える
12

Brian はすばらしいリストを持っています。

于 2008-09-16T11:29:51.593 に答える
8

Scott Wheeler によるWeb ページC++ Pitfalls では、主な C++ の落とし穴のいくつかを取り上げています。

于 2008-08-27T15:09:09.987 に答える
8

具体的なヒントではありませんが、一般的なガイドライン: ソースを確認してください。C++ は古い言語であり、長年にわたって多くの変化を遂げてきました。それに伴い、ベスト プラクティスも変更されましたが、残念ながら、まだ多くの古い情報が残っています。ここには非常に優れた本がいくつかあります。Scott Meyers の C++ 本はどれも 2 番目に購入できます。Boost と、Boost で使用されるコーディング スタイルに慣れてください。そのプロジェクトに関係する人々は、C++ 設計の最先端にいます。

車輪を再発明しないでください。STL と Boost に精通し、可能な限りそれらの機能を使用して独自の機能を開発してください。特に、非常に正当な理由がない限り、STL 文字列とコレクションを使用してください。auto_ptr と Boost スマート ポインター ライブラリをよく理解し、各タイプのスマート ポインターがどのような状況で使用されることを意図しているかを理解してから、他の方法では生のポインターを使用していた可能性があるあらゆる場所でスマート ポインターを使用します。あなたのコードは同じように効率的で、メモリ リークが発生しにくくなります。

C スタイルのキャストの代わりに、static_cast、dynamic_cast、const_cast、および reinterpret_cast を使用します。C スタイルのキャストとは異なり、要求していると思っているのとは異なるタイプのキャストを本当に要求しているかどうかを知らせてくれます。そしてそれらは視覚的に目立ち、キャストが行われていることを読者に警告します.

于 2008-09-22T21:25:16.580 に答える
6

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.
}
于 2008-11-11T16:46:27.647 に答える
6

難しい方法で学ばなければよかったと思う 2 つの落とし穴:

(1) 多くの出力 (printf など) はデフォルトでバッファリングされます。クラッシュするコードをデバッグしていて、バッファリングされた debug ステートメントを使用している場合、最後に表示される出力は、実際にはコード内で最後に検出された print ステートメントではない可能性があります。解決策は、各デバッグ出力の後にバッファをフラッシュする (またはバッファリングを完全にオフにする) ことです。

(2) 初期化には注意してください - (a) クラスインスタンスをグローバル / スタティックとして避ける。(b) ポインターの NULL などの些細な値であっても、すべてのメンバー変数を ctor 内の安全な値に初期化しようとします。

理由: グローバル オブジェクトの初期化の順序は保証されていません (globals には静的変数が含まれます)。そのため、オブジェクト Y の前にオブジェクト X が初期化されることに依存しているため、非決定論的に失敗したように見えるコードになる可能性があります。クラスのメンバー bool や enum などのプリミティブ型の変数を使用すると、予期しない状況で異なる値になることがあります。これもまた、動作が非常に非決定的に見える場合があります。

于 2008-08-29T06:39:53.970 に答える
6

すでに何度か言及しましたが、Scott Meyers の本「Effective C++ 」と「 Effective STL」は、C++ を支援するための金の価値があります。

考えてみれば、Steven Dewhurst のC++ Gotchasも優れた「塹壕から」のリソースです。独自の例外のローリングとその構築方法に関する彼の項目は、あるプロジェクトで私を本当に助けてくれました。

于 2008-08-27T15:37:14.037 に答える
4

ここに私が陥る不幸があったいくつかの穴があります. これらすべてには、私を驚かせた行動に噛まれて初めて理解した正当な理由があります.

  • 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`
    
于 2008-09-07T18:48:53.720 に答える
4

C++ Gotchasが役に立つかもしれません。

于 2008-08-27T15:07:51.783 に答える
3
  1. C ++FAQLiteを読んでいない。それは多くの悪い(そして良い!)慣行を説明しています。
  2. Boostを使用していません。可能な場合はBoostを利用することで、多くのフラストレーションを軽減できます。
于 2008-09-22T21:37:03.213 に答える
3

初心者の開発者にとって最も重要な落とし穴は、C と C++ の混同を避けることです。C++ は、単に優れた C やクラスを備えた C として扱われるべきではありません。これは、その能力を削ぎ落とし、危険にさらす可能性があるためです (特に C のようにメモリを使用する場合)。

于 2008-08-27T15:21:23.903 に答える
3

PRQA には、 Scott Meyers、Bjarne Stroustrop、および Herb Sutter の書籍に基づいた優れた無料の C++ コーディング標準があります。このすべての情報を 1 つのドキュメントにまとめます。

于 2008-09-01T02:03:56.793 に答える
3

boost.orgをチェックしてください。多くの追加機能、特にスマート ポインターの実装を提供します。

于 2008-08-27T15:29:04.540 に答える
2

疑似クラスや準クラスは避けてください...基本的にオーバーデザイン。

于 2008-09-22T21:34:24.890 に答える
2

スマート ポインターとコンテナー クラスを使用する場合は注意してください。

于 2008-09-07T19:34:10.227 に答える
2

基本クラスのデストラクタ virtual を定義するのを忘れています。これはdelete、 Base* を呼び出しても派生部分が破棄されないことを意味します。

于 2008-11-12T19:47:01.293 に答える
1

名前空間をまっすぐに保ちます(構造体、クラス、名前空間、および使用法を含む)。これは、プログラムがコンパイルされないときの私の一番のフラストレーションです。

于 2008-11-11T17:38:46.153 に答える
1

混乱させるには、ストレートポインタをよく使用します。代わりに、ほとんどすべてにRAIIを使用します。もちろん、適切なスマートポインターを使用するようにしてください。ハンドルまたはポインタ型クラスの外のどこかに「delete」と書くと、間違っている可能性が非常に高くなります。

于 2008-11-11T17:43:24.240 に答える
1
  • static_cast仮想基本クラスのダウンキャスト

そうではありません...私の誤解について:実際Aにはそうではないのに、以下は仮想基本クラスだと思いました。これは、10.3.1 によると、ポリモーフィック クラスです。ここを使えstatic_castば大丈夫そうです。

struct B { virtual ~B() {} };

struct D : B { };

要約すると、はい、これは危険な落とし穴です。

于 2008-11-11T20:40:00.170 に答える
1

「 C++ の落とし穴: コーディングと設計における一般的な問題の回避」という本を読んでください。

于 2008-09-22T21:40:23.940 に答える
1
  • ブリズパスタ。よく見かける大物です…

  • 初期化されていない変数は、私の学生が犯す大きな間違いです。多くの Java 関係者は、「int カウンター」と言うだけではカウンターが 0 に設定されないことを忘れています。h ファイルで変数を定義する必要があるため (そしてオブジェクトのコンストラクター/セットアップで変数を初期化する必要があるため)、忘れがちです。

  • forループ/配列アクセスでの off-by-one エラー。

  • voodoo の起動時にオブジェクト コードが適切にクリーニングされない。

于 2008-11-11T20:33:14.677 に答える
0

エッセイ/記事のポインタ、リファレンス、値は非常に便利です。落とし穴やグッドプラクティスを回避することは避けてください。主にC++のプログラミングのヒントを含むサイト全体を閲覧することもできます。

于 2008-11-15T21:34:30.427 に答える
0

ポインターを逆参照する前に、必ずポインターを確認してください。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
}
于 2008-11-11T17:00:34.810 に答える
0

を忘れて&、参照の代わりにコピーを作成する。

これは、さまざまな方法で2回発生しました。

  • 1 つのインスタンスが引数リストに含まれていたため、大きなオブジェクトがスタックに置かれ、スタック オーバーフローと組み込みシステムのクラッシュが発生しました。

  • &オブジェクトがコピーされたという効果で、インスタンス変数の を忘れました。コピーのリスナーとして登録した後、なぜ元のオブジェクトからコールバックを取得できなかったのか疑問に思いました。

違いが小さくてわかりにくいため、どちらもかなり見つけにくい場所です。それ以外の場合、オブジェクトと参照は構文的に同じように使用されます。

于 2010-10-21T09:52:38.663 に答える
0

私は長年 C++ の開発に携わってきました。何年も前に私が抱えていた問題の簡単な要約を書きました。標準に準拠したコンパイラはもはや実際には問題ではありませんが、概要を説明した他の落とし穴がまだ有効であると思われます。

于 2009-04-16T00:46:17.817 に答える
0

意図は(x == 10)

if (x = 10) {
    //Do something
}

自分では絶対に間違えないと思っていたのですが、最近やってしまいました。

于 2008-11-11T17:25:50.703 に答える
-1
#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);
}
于 2008-11-12T03:22:42.340 に答える