web-dev-qa-db-ja.com

関数から配列へのポインターを返すC ++の正しい方法

私はC++を初めて使用し、ポインターを避けています。オンラインで読んだものから、配列を返すことはできませんが、配列へのポインタを返すことはできます。私はそれをテストするために小さなコードを作成し、これがこれを行うための通常の/正しい方法であるかどうか疑問に思っていました:

#include <iostream>
using namespace std;

int* test (int in[5]) {
    int* out = in;
    return out;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int* pArr = test(arr);
    for (int i = 0; i < 5; i++) cout<<pArr[i]<<endl;
    cout<<endl;
    return 0;
}

編集:これは良くないようです。どのように書き直すべきですか?

int* test (int a[5], int b[5]) {
    int c[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    int* out = c;
    return out;
}
23
asimes

現状のコードは正しいですが、実際のシナリオでどのように使用できるか、または使用されるかを理解するのに苦労しています。そうは言っても、関数からポインターを返す際にはいくつかの注意事項に注意してください。

  • 構文_int arr[5];_で配列を作成すると、スタックに割り当てられ、関数に対してローカルになります。
  • C++では、この配列へのポインタを返すことができますが、メモリを使用するのはundefined behaviorですローカルスコープ外のこのポインターが指す。 実世界のアナロジーを使用したこの素晴らしい答え を読んで、私が今まで説明できたことよりもはるかに明確な理解を得てください。
  • アレイのメモリが削除されていないことを保証できる場合、スコープ外でアレイを使用できます。あなたの場合、これはarrtest()に渡すときに当てはまります。
  • メモリリークを心配せずに、動的に割り当てられた配列へのポインタを渡したい場合は、_std::unique_ptr_/_std::shared_ptr<>_を読んでください。

編集-行列乗算のユースケースに答えるために

2つのオプションがあります。素朴な方法は_std::unique_ptr_/_std::shared_ptr<>_を使用することです。現代のC++の方法は、Matrixクラスを使用して_operator *_をオーバーロードし、乗算の結果をコピーして回避する場合は、必ず新しい_rvalue references_を使用する必要があります関数の。 _copy constructor_、_operator =_、およびdestructorに加えて、_move constructor_および_move assignment operator_も必要です。 this search の質問と回答を読んで、これを達成する方法についてより多くの洞察を得てください。

編集2-追加された質問への回答

_int* test (int a[5], int b[5]) {
    int *c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}
_

これをint *res = test(a,b);として使用している場合は、コードの後半で_delete []res_を呼び出して、test()関数で割り当てられたメモリを解放する必要があります。問題は、deleteをいつ呼び出すかを手動で追跡することが非常に難しいことです。したがって、回答で概説されているように、それをどのように扱うかのアプローチ。

16
vvnraman

コードは問題ありません。ただし、配列へのポインターを返し、その配列が範囲外になった場合は、そのポインターを使用しないでください。例:

_int* test (void)
{
    int out[5];
    return out;
}
_

test()が戻ると、outはもう存在しないため、上記は機能しません。返されたポインターは使用しないでください。 doを使用すると、使用すべきでないメモリの読み取り/書き込みが行われます。

元のコードでは、main()が戻ると、arr配列はスコープ外になります。 main()から戻ることは、プログラムが終了していることも意味するため、明らかに問題はありません。

動き続けてスコープ外に出られないものが必要な場合は、newで割り当てる必要があります。

_int* test (void)
{
    int* out = new int[5];
    return out;
}
_

返されるポインターは常に有効です。ただし、_delete[]_を使用して、完了したら再度削除してください。

_int* array = test();
// ...
// Done with the array.
delete[] array;
_

削除することが、使用するメモリを再利用する唯一の方法です。

7
Nikos C.

新しい質問に対する新しい回答:

関数から自動変数(int c[5])へのポインターを返すことはできません。自動変数は、ブロック(この場合は関数)を返すreturnでその有効期間を終了します。したがって、存在しない配列へのポインターを返しています。

変数を動的にします:

int* test (int a[5], int b[5]) {
    int* c = new int[5];
    for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
    return c;
}

または、std::arrayを使用するように実装を変更します。

std::array<int,5> test (const std::array<int,5>& a, const std::array<int,5>& b) 
{
   std::array<int,5> c;
   for (int i = 0; i < 5; i++) c[i] = a[i]+b[i];
   return c;
}

コンパイラがstd::arrayを提供しない場合、配列を含む単純な構造体に置き換えることができます:

struct array_int_5 { 
   int data[5];
   int& operator [](int i) { return data[i]; } 
   int operator const [](int i) { return data[i]; } 
};

古い質問に対する古い回答:

あなたのコードは正しい、そして...うーん、まあ、...役に立たない。追加の関数なしで配列をポインターに割り当てることができるため(関数で既にこれを使用していることに注意してください):

int arr[5] = {1, 2, 3, 4, 5};
//int* pArr = test(arr);
int* pArr = arr;

関数のその他の署名:

int* test (int in[5])

以下と同等です:

int* test (int* in)

だから意味がないことがわかります。

ただし、このシグネチャは、ポインターではなく配列を取ります。

int* test (int (&in)[5])
2
PiotrNycz

配列を参照する変数は、基本的に最初の要素へのポインターです。そのため、本質的に同じものであるため、配列へのポインターを正当に返すことができます。これを自分でチェックしてください:

#include <assert.h>

int main() {
  int a[] = {1, 2, 3, 4, 5}; 

  int* pArr = a;
  int* pFirstElem = &(a[0]);

  assert(a == pArr);
  assert(a == pFirstElem);

  return 0;
}

また、これは、関数への配列の受け渡しがポインタを介して(およびint in[5]を介してではなく)、場合によっては長さとともに行われることを意味します配列の:

int* test(int* in, int len) {
    int* out = in;
    return out;
}

とはいえ、ポインターを(完全に理解せずに)使用することはかなり危険です。たとえば、スタックに割り当てられたスコープを外れた配列を参照すると、undefined behaviorが生成されます。

#include <iostream>

using namespace std;

int main() {
  int* pArr = 0;
  {
    int a[] = {1, 2, 3, 4, 5};
    pArr = a; // or test(a) if you wish
  }
  // a[] went out of scope here, but pArr holds a pointer to it

  // all bets are off, this can output "1", output 1st chapter
  // of "Romeo and Juliet", crash the program or destroy the
  // universe
  cout << pArr[0] << endl; // WRONG!

  return 0;
}

したがって、十分な能力がないと感じる場合は、std::vectorを使用してください。

[更新された質問への回答]

test関数を記述する正しい方法は次のいずれかです。

void test(int* a, int* b, int* c, int len) {
  for (int i = 0; i < len; ++i) c[i] = a[i] + b[i];
}
...
int main() {
   int a[5] = {...}, b[5] = {...}, c[5] = {};
   test(a, b, c, 5);
   // c now holds the result
}

またはこれ(std::vectorを使用):

#include <vector>

vector<int> test(const vector<int>& a, const vector<int>& b) {
  vector<int> result(a.size());
  for (int i = 0; i < a.size(); ++i) {
    result[i] = a[i] + b[i];
  }
  return result; // copy will be elided
}
1
dorserg

実際のアプリでは、配列を返す方法はoutパラメーターを使用と呼ばれます。もちろん、実際に配列へのポインターを返す必要はありません。呼び出し元が既に持っているので、配列に入力するだけです。オーバーフローしないように、配列のサイズを指定する別の引数を渡すことも一般的です。

Outパラメーターを使用すると、結果を格納するために配列がどれだけの大きさを必要とするかを呼び出し側が知らないという欠点があります。その場合、std :: vectorまたは同様の配列クラスインスタンスを返すことができます。

0
user1610015

あなたのコード(よさそうだ)は、配列へのポインタを返しません。 最初の要素配列へのポインタを返します。

実際、それは通常あなたがやりたいことです。配列のほとんどの操作は、配列全体へのポインターではなく、個々の要素へのポインターを介して行われます。

can配列へのポインタを定義します。たとえば次のようになります。

double (*p)[42];

psの42要素配列へのポインターとしてdoubleを定義します。それに関する大きな問題は、配列の要素の数を型の一部として指定する必要があることです。その数はコンパイル時の定数でなければなりません。配列を扱うほとんどのプログラムは、さまざまなサイズの配列を扱う必要があります。指定された配列のサイズは作成後も変化しませんが、初期サイズはコンパイル時に必ずしもわかっているわけではなく、異なる配列オブジェクトは異なるサイズを持つことができます。

配列の最初の要素へのポインターを使用すると、ポインター演算またはインデックス演算子[]を使用して、配列の要素を走査できます。 しかしポインターは、配列に含まれる要素の数を示しません。通常は自分で追跡する必要があります。

関数がcreate配列を必要とし、その最初の要素へのポインターを返す場合、いくつかの方法の1つで、その配列のストレージを自分で管理する必要があります。おそらくサイズを指定する別の引数とともに、呼び出し元に配列オブジェクト(の最初の要素)へのポインターを渡すことができます。これは、呼び出し元が配列の大きさを知る必要があることを意味します。または、関数は、関数内で定義された静的配列(の最初の要素)へのポインターを返すことができます。これは、配列のサイズが固定され、同じ配列が関数の2回目の呼び出しによって上書きされることを意味します。または、関数はヒープに配列を割り当てることができます。これにより、後で呼び出し元が割り当てを解除する責任が生じます。

これまでに書いたものはすべてCとC++に共通しており、実際にはC++よりもCのスタイルの方がはるかに多くなっています。 comp.lang.c FAQ のセクション6では、Cでの配列とポインターの動作について説明しています。

ただし、C++で記述している場合は、おそらくC++のイディオムを使用した方が良いでしょう。たとえば、C++標準ライブラリには、<vector><array>などのコンテナクラスを定義する多くのヘッダーが用意されており、これらのほとんどを処理します。生の配列とポインタを使用する特別な理由がない限り、おそらくC++コンテナを使用する方が良いでしょう。

EDIT:この回答を入力しているときに質問を編集したと思います。質問の最後にある新しいコードは、オブザーバーとしては役に立たない。関数が返るとすぐに存在しなくなるオブジェクトへのポインタを返します。私は代替案をカバーしたと思います。

0
Keith Thompson

配列を返すことができます

の代わりに

int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
int* m3 = test(m1, m2);

書きます

struct mystruct
{
  int arr[5];
};


int m1[5] = {1, 2, 3, 4, 5};
int m2[5] = {6, 7, 8, 9, 10};
mystruct m3 = test(m1,m2);

テストは次のようになります

struct mystruct test(int m1[5], int m2[5])
{
  struct mystruct s;
  for (int i = 0; i < 5; ++i ) s.arr[i]=m1[i]+m2[i];
  return s;
}

コピーしているのであまり効率的ではない

0
Anders