1

私はJavaとCの両方でプログラミングしましたが、今はC++で手を汚そうとしています。

このコードを考えると:

class Booth {

private :
          int tickets_sold;
public :
          int get_tickets_sold();
          void set_tickets_sold();
};

Javaでは、の値が必要な場合はいつでもtickets_sold、ゲッターを繰り返し呼び出します。

例えば:

if (obj.get_tickets_sold() > 50 && obj.get_tickets_sold() < 75){
     //do something
}

CIでは、構造内の特定の変数の値を取得するだけです。

if( obj_t->tickets_sold > 50 && obj_t->tickets_sold < 75){
    //do something
}

したがって、Cで構造体を使用している間、Javaで行う2つの呼び出し、つまり2つのゲッターを節約します。つまり、これらが実際の呼び出しなのか、Javaが何らかの形でそれらの呼び出しをインライン化するのかさえわかりません。

私のポイントは、C ++のJavaでも使用したのと同じ手法を使用する場合、getterメンバー関数へのこれらの2つの呼び出しにコストがかかるのでしょうか、それともコンパイラーがコードをインライン化することを何らかの形で知っているのでしょうか。(したがって、関数呼び出しのオーバーヘッドを完全に削減しますか?)

または、次を使用することをお勧めします。

int num_tickets = 0;

if ( (num_tickets = obj.get_ticket_sold()) > 50 && num_tickets < 75){
    //do something
}

タイトなコードを書き、不要な関数呼び出しを避けたいのですが、Javaでこれを気にします。なぜなら、私たち全員がその理由を知っているからです。ただし、コードを読みやすくし、privateandpublicキーワードを使用して、実行する内容を正しく反映する必要があります。

4

7 に答える 7

8

プログラムが遅すぎない限り、それは実際には問題ではありません。コードの99.9999%では、関数呼び出しのオーバーヘッドは重要ではありません。最も明確で、保守が容易で、理解しやすいコードを記述し、パフォーマンスのホットスポットがある場合は、それがどこにあるかを知ってから、パフォーマンスの調整を開始します。

とは言うものの、最新のC ++コンパイラー(および一部のリンカー)は、関数、特にこのような単純な関数をインライン化できます。

于 2011-02-24T04:38:29.750 に答える
5

あなたが言語を学んでいるだけなら、あなたは本当にこれについて心配するべきではありません。他の方法で証明されるまで、十分に速く検討してください。とはいえ、ここには誤解を招く答えや不完全な答えがたくさんあるので、記録のために、微妙な意味のいくつかを具体化します。あなたのクラスを考えてみましょう:

class Booth
{
  public:
    int get_tickets_sold();
    void set_tickets_sold();
  private:
    int tickets_sold;
};

getおよびset関数の実装(定義と呼ばれる)はまだ指定されていません。クラス宣言内で関数本体を指定した場合、コンパイラーは、それらがインライン化されることを暗黙的に要求したと見なします(ただし、関数本体が大きすぎる場合は無視できます)。後でキーワードを使用してそれらを指定するとinline、まさに安全な効果があります。要約すると...

class Booth
{
  public:
    int get_tickets_sold() { return tickets_sold; }
    ...

...と...

class Booth
{
  public:
    int get_tickets_sold();
    ...
};

inline int Booth::get_tickets_sold() { return tickets_sold; }

...同等です(少なくとも、標準で期待されることに関しては、個々のコンパイラのヒューリスティックは異なる場合があります。インライン化は、コンパイラが自由に無視できるようにする要求です)。

関数本体が後でinlineキーワードなしで指定された場合、コンパイラーはそれらをインライン化する義務を負いませんが、それでもそうすることを選択できます。それらが同じ変換単位(つまり、コンパイルしている.cc / .cpp / .c ++ / etc。「実装」ファイルまたは直接または間接的に含まれているヘッダー)にある場合は、そうなる可能性がはるかに高くなります。 実装がリンク時にのみ利用可能である場合、関数はまったくインライン化されない可能性がありますが、特定のコンパイラとリンカが相互作用して連携する方法によって異なります。それは単に最適化を可能にし、魔法を期待することの問題ではありません。これを証明するために、次のコードを検討してください。

// inline.h:
void f();

// inline.cc:
#include <cstdio>
void f() { printf("f()\n"); }

// inline_app.cc:
#include "inline.h"
int main() { f(); }

これを構築する:

g++ -O4 -c inline.cc
g++ -O4 -o inline_app inline_app.cc inline.o

インライン化の調査:

$ gdb inline_app 
...
(gdb) break main
Breakpoint 1 at 0x80483f3
(gdb) break f
Breakpoint 2 at 0x8048416
(gdb) run
Starting program: /home/delroton/dev/inline_app 

Breakpoint 1, 0x080483f3 in main ()
(gdb) next
Single stepping until exit from function main, 
which has no line number information.

Breakpoint 2, 0x08048416 in f ()
(gdb) step
Single stepping until exit from function _Z1fv, 
which has no line number information.
f()
0x080483fb in main ()
(gdb) 

実行がmain()の0x080483f3からf()の0x08048416になり、main()の0x080483fbに戻ったことに注意してください...明らかにインライン化されていません。これは、関数の実装が簡単であるという理由だけでインライン化を期待できないことを示しています。

この例は、オブジェクトファイルの静的リンクを使用していることに注意してください。明らかに、ライブラリファイルを使用する場合は、クライアントコードを再コンパイルせずにライブラリを更新できるように、関数のインライン化を特に避けたい場合があります。とにかくロード時に暗黙的にリンクが行われる共有ライブラリの場合はさらに便利です。

非常に多くの場合、些細な関数を提供するクラスはinline、パフォーマンスが重要なループ内でそれらの関数が呼び出されることが期待できる場合、2つの形式の期待されるインライン関数定義(つまり、クラス内またはキーワード付き)を使用しますが、対抗する考慮事項は、関数の実装への変更を取得するために、単に再リンクするのではなく、クライアントコードを強制的に再コンパイルし(比較的遅く、自動トリガーがない可能性があります)、再リンクします(高速、共有ライブラリは次の実行で発生します)。

この種の考慮事項は厄介ですが、これらのトレードオフを慎重に管理することで、企業でのCおよびC ++の使用を、数千万の回線および数千の個別のプロジェクトに拡張でき、すべてが数十年にわたってさまざまなライブラリを共有できます。

もう1つの小さな詳細:球場の図として、アウトオブラインのget / set関数は、通常、同等のインラインコードよりも約1桁(10倍)遅くなります。これは、CPU、コンパイラ、最適化レベル、変数タイプ、キャッシュのヒット/ミスなどによって明らかに異なります。

于 2011-02-24T04:51:21.277 に答える
4

いいえ、メンバー関数を繰り返し呼び出しても問題はありません。

それが単なるゲッター関数である場合、ほぼ確実にC ++コンパイラーによってインライン化され(少なくともリリース/最適化ビルドでは)、Java仮想マシンは特定の関数が頻繁に呼び出されていることを「把握」してそのために最適化する場合があります。したがって、一般的に関数を使用してもパフォーマンスが低下することはほとんどありません。

常に読みやすさを最初にコーディングする必要があります。もちろん、パフォーマンスを完全に無視する必要があるというわけではありませんが、パフォーマンスが許容できない場合は、いつでもコードのプロファイルを作成して、最も遅い部分がどこにあるかを確認できます。

また、getter関数の背後にある変数へのアクセスを制限することにより、変数をのメンバー関数にtickets_sold変更できる唯一のコードをほぼ保証できます。これにより、プログラムの動作に不変条件を適用できます。tickets_soldBooth

たとえば、tickets_sold明らかに負の値になることはありません。それは構造の不変量です。tickets_soldプライベートにし、メンバー関数がその不変条件に違反しないようにすることで、その不変条件を適用できます。このBoothクラスはtickets_sold、getter関数を介して「読み取り専用データメンバー」として他のすべてのユーザーが利用できるようにし、不変条件を保持します。

これをパブリック変数にするということは、誰でものデータを踏みにじることができることを意味しtickets_soldます。これは、基本的に、に不変条件を適用する能力を完全に破壊しますtickets_sold。これにより、誰かが負の数をに書き込むことが可能になりますがtickets_sold、これはもちろん無意味です。

于 2011-02-24T04:38:20.573 に答える
1

コンパイラは、このような関数呼び出しをインライン化する可能性が非常に高くなります。

于 2011-02-24T04:38:25.373 に答える
1
class Booth {
public:
    int get_tickets_sold() const { return tickets_sold; }

private:
    int tickets_sold;
};

あなたのコンパイラインラインget_tickets_soldでなければなりません、そうでなければ私は非常に驚かれることでしょう。そうでない場合は、新しいコンパイラを使用するか、最適化をオンにする必要があります。

于 2011-02-24T04:38:30.570 に答える
0

その塩に値するコンパイラは、ゲッターをメンバーに直接アクセスするように簡単に最適化します。最適化が明示的に無効になっている場合(デバッグビルドなど)、または脳死したコンパイラを使用している場合(この場合、実際のコンパイラではそれを捨てることを真剣に検討する必要があります)のみが発生しません。

于 2011-02-24T04:46:12.713 に答える
0

コンパイラーがあなたに代わって作業を行う可能性が非常に高いですが、一般に、このような場合、メンバーにconst参照へのアクセスを許可しない限り、Javaの観点からではなくCの観点からアプローチします。ただし、整数を処理する場合、通常、コピーに対してconst参照を使用する価値はほとんどありません(両方とも4バイトであるため、少なくとも32ビット環境では)。したがって、ここでの例は実際には適切ではありません...おそらくこれはC++でゲッター/セッターを使用する理由を説明します。

class StringHolder
{
public:
  const std::string& get_string() { return my_string; }
  void set_string(const std::string& val) { if(!val.empty()) { my_string = val; } }
private
  std::string my_string;
}

これにより、追加のロジックを実行できるセッター以外の変更が防止されます。ただし、このような単純なクラスでは、このモデルの値はnilであり、このモデルを呼び出しているコーダーをより多くのタイプにしただけで、実際には値を追加していません。そのようなクラスの場合、ゲッター/セッターモデルはありません。

于 2011-02-24T04:46:38.827 に答える