web-dev-qa-db-ja.com

C ++が配列を返す関数をサポートしないのはなぜですか?

一部の言語では、Javaなどの通常の関数のように配列を返す関数を宣言することができます。

_public String[] funcarray() {
   String[] test = new String[]{"hi", "hello"};
   return test;
}
_

C++がint[] funcarray(){}のようなものをサポートしないのはなぜですか?配列を返すこともできますが、そのような関数を作成するのは本当に面倒です。また、文字列は単なるcharの配列であると聞いたことがあります。 C++で文字列を返すことができるなら、なぜ配列ではないのでしょうか?

45
Lockhead

簡潔に言うと、それは単に設計上の決定だったと思います。より具体的には、本当に理由を知りたい場合は、ゼロから作業する必要があります。

最初にCについて考えましょう。 C言語では、「参照渡し」と「値渡し」の間には明確な違いがあります。軽く扱うために、Cの配列の名前は実際には単なるポインタです。すべての意図と目的の違いは、(一般的に)割り当てにあります。コード

int array[n];

(32ビットシステムで)4 * nバイトのメモリをスタックに作成し、宣言を行うコードブロックのスコープに関連付けます。順番に、

int* array = (int*) malloc(sizeof(int)*n);

同じ量のメモリを作成しますが、ヒープ上にあります。この場合、そのメモリ内のものはスコープに関連付けられておらず、メモリへの参照のみがスコープによって制限されています。ここに値渡しと参照渡しがあります。値渡しとは、ご存じのように、何かが関数に渡されたり、関数から返されたりしたときに、渡される「もの」が変数を評価した結果であることを意味します。言い換えると、

int n = 4;
printf("%d", n);

構造体nは4に評価されるため、数値4が出力されます(これが基本の場合は申し訳ありませんが、すべての基底をカバーしたいだけです)。この4は、プログラムのメモリ空間とはまったく関係がなく、単なるリテラルなので、その4がコンテキストを持つスコープを離れると、それは失われます。参照渡しについてはどうですか?参照渡しは、関数のコンテキストでも同じです。単に渡される構成を評価するだけです。唯一の違いは、渡された「もの」を評価した後、評価の結果をメモリアドレスとして使用することです。私はかつて、参照渡しするようなものはなく、巧妙な値を渡すための方法であると述べることを好んだ特定の皮肉なCSインストラクターがいました。本当に、彼は正しい。そこで、関数の観点からスコープについて考えます。配列の戻り値の型を使用できるとしましょう。

int[] foo(args){
    result[n];
    // Some code
    return result;
}

ここでの問題は、結果が配列の0番目の要素のアドレスに評価されることです。ただし、この関数の外部から(戻り値を介して)このメモリにアクセスしようとすると、作業中のスコープ(関数呼び出しのスタック)にないメモリにアクセスしようとしているため、問題が発生します。したがって、これを回避する方法は、標準の「参照渡し」のjiggery-pokeryを使用することです。

int* foo(args){
    int* result = (int*) malloc(sizeof(int)*n));
    // Some code
    return result;
}

配列の0番目の要素を指すメモリアドレスはまだ取得していますが、そのメモリにアクセスできます。

私のポイントは何ですか? Javaでは、「すべてが値で渡される」と断言するのが一般的です。これは本当です。上記の同じ皮肉な講師は、JavaおよびOOP一般的に:すべては単なるポインタです。そして彼も正しいです。 Javaは実際には値渡しであり、これらの値のほとんどすべては実際にはメモリアドレスです。したがって、Javaでは、言語は配列または文字列を返すことができますが、ポインタが付いたバージョンにアクセスできます。また、メモリも管理されます。自動メモリ管理は便利ですが、効率的ではありません。

これでC++が登場します。 C++が発明された全体の理由は、Bjarne StroustrupがPhDの作業中にSimula(基本的には元のOOPL)を実験していて、概念的には素晴らしかったと思っていたためです。そして彼はC with Classesと呼ばれるものに取り組み始めました、それはC++に改名されました。そうすることで、彼の目標は、Simulaの優れた機能のいくつかを取り入れながら、強力で高速なプログラミング言語を作ることでした。彼はすでに伝説的なパフォーマンスのためにCを拡張することを選択しました、そして1つのトレードオフは彼が他のOOPLのような大規模に自動メモリ管理またはガベージコレクションを実装しないことを選択したことです。テンプレートクラスの1つから配列を返すのは、クラスを使用しているからです。しかし、C配列を返したい場合は、Cの方法で行う必要があります。言い換えると、C++はJavaが行うのとまったく同じ方法で配列を正確に返すことをサポートします;それはあなたのためにすべての仕事をしません。デンマークの男もそれがスロー。

61
Doug Stephen

C++はそれをサポートしています-よく並べ替え:

vector< string> func()
{
   vector<string> res;
   res.Push_back( "hello" );
   res.Push_back( "world" );
   return res;
}

C sort-ofでもそれをサポートします:

struct somearray
{
  struct somestruct d[50];
};

struct somearray func()
{
   struct somearray res;
   for( int i = 0; i < 50; ++i )
   {
      res.d[i] = whatever;
   }
   // fill them all in
   return res;
}

std::stringはクラスですが、文字列を言うときはおそらくリテラルを意味します。関数からリテラルを安全に返すことができますが、実際には任意の配列を静的に作成して関数から返すことができます。文字列リテラルの場合のようにconst(読み取り専用)配列の場合、これはスレッドセーフになります。

ただし、返される配列はポインタに劣化するため、返されるだけではそのサイズを計算できません。

可能であれば、配列を返すことは、コンパイラが呼び出しスタックを作成する必要があり、配列がl値ではないために呼び出し関数でそれを受け取るという問題がある場合、最初に固定長にする必要があります初期化では新しい変数を使用する必要があるため、実用的ではありません。戻り値に特別な表記法を使用していたとしても、同じ理由で1つを返すことも非現実的かもしれません。

Cの初期の頃は、すべての変数を関数の先頭で宣言する必要があり、最初の使用時に宣言するだけではできなかったことを覚えておいてください。したがって、当時は不可能でした。

彼らは、配列を構造体に配置する回避策を提供しました。これは、同じ呼び出し規約を使用するため、C++に残しておく必要がある方法です。

注:Javaなどの言語では、配列はクラスです。あなたは新しいものを作成します。それらを再割り当てできます(これらはl値です)。

31
CashCow

C(および下位互換性のためにC++)の配列には、他の型とは異なる特別なセマンティクスがあります。特に、残りの型については、Cには値渡しのセマンティクスしかありませんが、配列の場合、値渡し構文の効果は参照渡しを奇妙な方法でシミュレートします。

関数シグネチャでは、型T型のN要素の配列の引数がTへのポインタに変換されます。関数の引数として配列を関数に渡す関数呼び出しでは、配列はdecayから配列へ最初の要素へのポインターになり、そのポインターが関数にコピーされます。

配列に対するこの特別な扱い(値によって渡すことはできません)のため、値によって返すこともできません。 Cではポインタを返すことができ、C++では参照を返すこともできますが、配列自体をスタックに割り当てることはできません。

考えてみれば、配列は動的に割り当てられ、ポインタ/参照を返すだけなので、これは質問で使用している言語と同じです。

一方、C++言語では、現在の標準でstd::vectorを使用する(コンテンツが動的に割り当てられる)や、次の標準でstd::arrayを使用するなど、その特定の問題に対するさまざまなソリューションを実現できます(コンテンツはスタックですが、コンパイラでコピーを省略できない場合は各要素をコピーする必要があるため、コストが高くなる可能性があります。実際、boost::arrayのような既成のライブラリを使用することにより、現在の標準で同じタイプのアプローチを使用できます。

「その配列は関数内で宣言されるため、関数から配列を返すことはできません。その位置はスタックフレームになります。ただし、関数が終了するとスタックフレームは消去されます。関数はスタックフレームから戻り値をコピーして返す必要があります場所、それは配列では不可能です。」

ここの議論から:

http://forum.codecall.net/c-c/32457-function-return-array-c.html

8
Orbit

C++では、Cから継承した配列の代わりにvector <>を使用するという人もいます。

では、なぜC++ではC配列を返すことができないのでしょうか。 Cはそうしないからです。

なぜCはそうしないのですか? CはBから進化したため、配列を返す型なし言語はまったく意味がありません。 Bに型を追加するとき、配列を返すことができるようにすることは意味がありましたが、一部のBイディオムを有効に保ち、BからCへのプログラムの変換を容易にするために行われませんでした。それ以来、可能性はC配列をより使いやすくすることは、既存のコードを壊しすぎるため、常に拒否され(さらには考慮もされず)なります。

7
AProgrammer

配列へのポインタを返すことができます。後でメモリを解放することに注意してください。

public std::string* funcarray() {
    std::string* test = new std::string[2];
    test[0] = "hi";
    test[1] = "hello";
    return test;
}

// somewhere else:
std::string* arr = funcarray();
std::cout << arr[0] << " MisterSir" << std::endl;
delete[] arr;

または、std :: vectorのようなstd名前空間のコンテナの1つを使用することもできます。

3
Jordi

「なぜC++は次のようなものをサポートしないのか」:意味がなさそうだからJavaまたはPHPのような参照ベースの言語では、メモリ管理はガベージコレクションに基づいています。参照のないメモリの部分(プログラムの変数がそれをポイントしていない)は自動的に解放されます。 。このコンテキストでは、メモリを割り当て、参照を気楽に渡すことができます。

C++コードはマシンコードに変換され、GCは定義されていません。したがって、CおよびC++には、メモリブロックのownershipの強い意味があります。行ったポインタがいつでも解放できるかどうか(実際にはshoud使用後に解放する)、またはポインタがあるかどうかを知る必要がありますメモリの共有部分への解放。これは完全に解放する必要があります。

この環境では、配列が関数とやり取りされるたびに配列の無限コピーを要求しても何も起こりません。 cのような言語でデータの配列を管理することは、はるかに複雑なタスクです。万能のソリューションはありません。メモリを解放するタイミングを知る必要があります。

関数によって返された配列は常にコピー(自分で解放する)ですか、それともコピーを作成する必要がありますか?配列へのポインタの配列を取得することで、どうして勝ちますか?

2
vbence

配列の代わりに_std::vector<>_を返します。一般に、配列はC++ではうまく機能しないため、通常は避ける必要があります。

また、stringデータ型は単なる文字の配列ではありませんが、「引用文字列」はそうです。 stringは文字の配列を管理し、.c_str()を使用してアクセスできますが、stringにはそれだけではありません。

1
David Thornley
1
sgokhales