6

私はC++にかなり慣れていないので、これはおそらく初心者の質問です。それは、私がかなり一般的だと思うことを行うための「適切な」スタイルに関するものです。

私は、その職務を実行する際に、呼び出し元が使用できるようにヒープにメモリを割り当てる関数を作成しています。この関数の良いプロトタイプがどのように見えるべきか興味があります。今私は持っています:

int f(char** buffer);

それを使用するには、次のように記述します。

char* data;
int data_length = f(&data);
// ...
delete[] data;

ただし、ポインターをポインターに渡しているという事実は、おそらくこれを間違った方法で行っていることを示唆しています。

誰か教えてくれませんか?

4

12 に答える 12

6

Cでは、それは多かれ少なかれ合法でした。

C++ では、通常、関数はそれを行うべきではありません。メモリがリークしないことを保証するために、 RAIIを使用するようにしてください。

そして今、「どうやってメモリ リークが発生するのでしょう。ここで呼び出しますdelete[]!」と言うかもしれませんが、行で例外がスローされた場合はどうなる// ...でしょうか?

関数の目的に応じて、考慮すべきオプションがいくつかあります。明らかな 1 つは、配列をベクトルに置き換えることです。

std::vector<char> f();

std::vector<char> data = f();
int data_length = data.size();
// ...
//delete[] data; 

ベクトルがスタックに割り当てられ、スコープ外になるとそのデストラクタが呼び出されるため、明示的に削除する必要がなくなりました。

コメントに応じて、上記はベクトルのコピーを意味することを言及する必要があります。これは潜在的に高価になる可能性があります。ほとんどのコンパイラは、f関数が複雑すぎない場合、そのコピーを最適化するので問題ありません。(関数があまり頻繁に呼び出されない場合、オーバーヘッドは問題になりませ)。しかし、それが起こらない場合は、代わりに空の配列をf参照によって関数に渡しf、新しいベクトルを返す代わりにそのデータを格納することができます。

コピーを返すパフォーマンスが受け入れられない場合、別の方法として、コンテナーの選択を完全に分離し、代わりにイテレーターを使用することもできます。

// definition of f
template <typename iter>
void f(iter out);

// use of f
std::vector<char> vec;
f(std::back_inserter(vec));

これで、通常のイテレータ操作 (*out現在の要素を参照または書き込むため、および++outイテレータを次の要素に進めるため) を使用できるようになりました。さらに重要なことに、すべての標準アルゴリズムが機能するようになりました。std::copyたとえば、データをイテレータにコピーするために使用できます。これは、関数が一連のデータを返す必要がある場合に、標準ライブラリによって通常選択されるアプローチです (つまり、これは良い考えです;))。

別のオプションは、割り当て/解放を担当する独自のオブジェクトを作成することです。

struct f { // simplified for the sake of example. In the real world, it should be given a proper copy constructor + assignment operator, or they should be made inaccessible to avoid copying the object
  f(){
    // do whatever the f function was originally meant to do here
    size = ???
    data = new char[size];
  }
  ~f() { delete[] data; }

int size;
char* data;
};

f data;
int data_length = data.size;
// ...
//delete[] data; 

また、割り当てはスタック上のオブジェクトによって管理されるため、明示的に削除する必要はなくなりました。後者は明らかに手間がかかり、エラーが発生する余地もあります。そのため、標準のベクター クラス (またはその他の標準ライブラリ コンポーネント) が機能する場合は、そちらを優先してください。この例は、状況に合わせてカスタマイズする必要がある場合のみです。

C++ の一般的な経験則は、「 RAII オブジェクトの外側にdeleteorを書いdelete[]ている場合、それは間違っています。RAII オブジェクトの外側に anewまたは `new[]を書いている場合、それは間違っています。結果がすぐにスマート ポインターに渡されない限り、間違っています。"

于 2009-07-23T14:09:39.057 に答える
5

「適切な」C++ では、その内部のどこかにメモリ割り当てを含むオブジェクトを返します。std::vector のようなもの。

于 2009-07-23T14:00:45.637 に答える
4

関数は、一部のメモリへのネイキッド ポインターを返すべきではありません。結局のところ、ポインターはコピーできます。次に、所有権の問題があります。実際にメモリを所有しているのは誰で、それを削除する必要がありますか? また、ネイキッド ポインターがスタック、ヒープ、または静的オブジェクトの単一のオブジェクトを指している可能性があるという問題もあります。これらの場所で配列を指すこともできます。返されるのはポインタだけだとすると、ユーザーはどのように知る必要があるでしょうか?

代わりにすべきことは、そのリソースを適切な方法で管理するオブジェクトを返すことです。(RAII を参照してください。) この場合のリソースは の配列でありchar、 astd::stringまたは a のいずれかstd::vectorが最適と思われるという事実を与えます。

int f(std::vector<char>& buffer);

std::vector<char> buffer;
int result = f(buffer);
于 2009-07-23T14:06:17.890 に答える
3

なぜ malloc() - void* malloc( size_t numberOfBytes ) と同じようにしないのでしょうか? このように、バイト数が入力パラメーターであり、割り当てられたブロック アドレスが戻り値です。

UPD: コメントでは、 f() は基本的にメモリの割り当て以外に何らかのアクションを実行すると言っています。この場合、 std::vector を使用する方がはるかに優れた方法です。

void f( std::vector<char>& buffer )
{
    buffer.clear();
    // generate data and add it to the vector
}

呼び出し元は、割り当てられたベクトルを渡すだけです。

 std::vector buffer;
 f( buffer );
 //f.size() now will return the number of elements to work with
于 2009-07-23T13:58:18.447 に答える
2

ポインターを参照渡し...

int f(char* &buffer)

ただし、これを始めたばかりの場合は、boost::shared_array などの参照カウント ポインターを使用してメモリを管理することを検討することをお勧めします。

例えば

int f(boost::shared_array<char> &buffer)
于 2009-07-23T14:00:22.813 に答える
1

RAII (Resource Acquisition Is Initialization) デザイン パターンを使用します。

http://en.wikipedia.org/wiki/RAII 用語の意味と概念を理解する - RAII (Resource Acquisition is Initialization)

于 2009-07-23T14:05:37.533 に答える
0

実際には、そのポインターをクラスに配置するのが賢明です。そうすれば、その破棄をより適切に制御でき、インターフェースがユーザーを混乱させることはほとんどありません。

class Cookie {
public:
   Cookie () : pointer (new char[100]) {};
   ~Cookie () {
      delete[] pointer;
   }
private:
   char * pointer;

   // Prevent copying. Otherwise we have to make these "smart" to prevent 
   // destruction issues.
   Cookie(const Cookie&);
   Cookie& operator=(const Cookie&);
};
于 2009-07-23T14:04:27.893 に答える
0

f()バッファーを使用してそれ (およびその長さ) を返すことがすべてである場合は、長さを返すだけにして、呼び出し元newにそれを持たせます。f()バッファで何かを行う場合は、多言語で提案されているとおりに行います。

もちろん、解決したい問題に対してより良い設計があるかもしれませんが、何かを提案するには、より多くのコンテキストを提供する必要があります.

于 2009-07-23T14:05:48.017 に答える
0

適切なスタイルは、おそらく char* を使用するのではなく、char* の使用目的に応じて std::vector または std::string を使用することです。

変更するパラメータを渡す問題については、ポインタを渡すのではなく、参照を渡します。あなたの場合:

int f(char*&);

そして、最初のアドバイスに従った場合:

int f(std::string&);

また

int f(std::vector<char>&);
于 2009-07-23T14:07:04.113 に答える
0

ポインタを返すだけです:

char * f() {
   return new char[100];
}

そうは言っても、おそらくこのような明示的な割り当てをいじる必要はありません-charの配列の代わりに、std::stringまたはstd::vector<char>代わりに使用してください。

于 2009-07-23T13:58:21.957 に答える
0

f がnew[]一致する a を行う場合、それは機能しますが、あまり慣用的ではありません。

f がデータを埋め、malloc()-alike だけではないと仮定すると、割り当てをstd::vector<char>

void f(std::vector<char> &buffer)
{
    // compute length
    int len = ...

    std::vector<char> data(len);

    // fill in data
    ...

    buffer.swap(data);
}

編集 -- 偽の * を署名から削除します

于 2009-07-23T14:02:40.220 に答える
-2

一次元配列を割り当てようとしていると思います。その場合は、ポインターにポインターを渡す必要はありません。

int f(char* &buffer)

十分なはずです。使用シナリオは次のようになります。

char* data;
int data_length = f(data);
// ...
delete[] data;
于 2009-07-23T14:01:37.430 に答える