構造体を戻り値として丸ごと返してもいいものか

ぼずお
記事: 1
登録日時: 12年前
住所: 中部地方

構造体を戻り値として丸ごと返してもいいものか

投稿記事 by ぼずお » 10年前

題名でうまく表せてない気がするのですが、話は単純で
要するにこんな感じです

CODE:

//何らかの構造体
struct Matrix4x4
{
	double	_m[4][4];
};

//構造体を返す関数
Matrix4x4 MakeSomeMatrix()
{
	Matrix4x4 out;
	//outに対する計算や数値の設定
	//....
	
	return out;
}
関数から構造体に値を設定して返したい場合、
教科書通りでは、以下のようにポインタや参照で渡すように習いますよね。

CODE:

//ポインタで受け取って値を設定する、教科書的な実装?
void MakeSomeMatrix(Matrix4x4* pOut)
{
	//*pOutに対する数値の設定
	//....
	
}
構造体を行列としたのは、ある程度大きな構造体を例としたかっただけで、特に意味はありません。

最近私は利便性から前者のように丸ごと返すことが多くなっています。

最近のハードは性能がいいので、パフォーマンスに関しては、私はあんまり考えません。
コンパイラの最適化がうまいことやってくれるとかなんとか?(RVO?)
単なるものぐさですね・・

こういったことはもちろんケースバイケースだと思うのですが、皆さんはどのように考えてどのようにされているでしょうか。
最後に編集したユーザー ぼずお on 2013年10月31日(木) 20:21 [ 編集 1 回目 ]

beatle
記事: 1281
登録日時: 12年前

Re: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by beatle » 10年前

私だったらスマートポインタで返すようにしますね。
std::shared_ptr MakeSomeMatrix();

アバター
GRAM
記事: 164
登録日時: 13年前

Re: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by GRAM » 10年前

むしろ自分は2つ目のコードの様に書くことのほうが稀ですね。あくまで数学的な概念に絞ると、関数の引数が非メンバの関数内でいじられるというのは
なんというか直観的じゃないんですよね。add( A, B, Out )みたいな書き方よりも Out = add( A, B )のほうがわかりやすい気がします。

ただし渡される引数が、関数名のの目的語になっている場合、参照渡し(もしくはポインタを使用する)になることも多いですね
MakeSomeMatrix(out)はoutがMakeの目的語じゃないので嫌ですが、Initialize(out) とか Transform( out )みたいな書き方はありだと思います。
これをout = Initialize() とか out = Transform()って書くと意味不明ですからね。
out = GetInitializedMatrix() ならいいんですけど
最後に編集したユーザー GRAM on 2013年11月01日(金) 11:09 [ 編集 1 回目 ]

アバター
やっくん
記事: 5
登録日時: 13年前

Re: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by やっくん » 10年前

初めまして。

1つ目のコードの場合、最適化が無くともクラスにムーブコンストラクタを用意して
受け取るというのもコピーせずに済むので良いかなと思ってます。
私は最適化に詳しくないのですが内部では同じことやってそうな気がします。(どうなんでしょうか・・・)

ぼずお
記事: 1
登録日時: 12年前
住所: 中部地方

RE: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by ぼずお » 10年前

レス頂いてありがとうございます。

「大きめの構造体」とだけ書いて、用途などを限定しなかったので、
話がわかりにくかったですね・・
「算術に使う構造体」とか「ポインタなどを含まないプレーンな構造体」など
的を絞ればよかったかもしれません。

構造体はC++ではクラスに含まれるので、広く見るといろんな捉え方ありますよね。


>beatleさん
おっとその発想がありましたか。
スマートポインタはあんまり使ったことないので以下間違っていたら済みません。

関数の中身はこのような形になるでしょうか。

CODE:

std::shared_ptr MakeSomeMatrix()
{
	std::shared_ptr mat(new Matrix44);
	//mat-> に対する値の設定
	return mat;
}

CODE:

//生成と計算(各関数は定義済みとして)
std::shared_ptr matA = MakeHogeMatrix();
std::shared_ptr matB = MakePiyoMatrix();
std::shared_ptr result = Multiply(matA, matB); 

//ローカル変数で受け取るとき(関数内で確保して、返すと即時破棄?)
Matrix4x4 localVal = *MakeSomeMatrix();
生成するたび計算するたびに動的確保が発生するのが気になりました。
パフォーマンス気にしないといった手前、動的確保にどうこう言うのは変ですかね・・
他の言語では普通なことっぽい?

スマートポインタでのやり取りを半ば強制してしまうのが少々難ありに感じました。

ただ、動的確保を前提とするような複雑なクラスの運用としては適しているかと思います。


>GRAMさん
考えていたことは、大体同じように感じます。

>なんというか直観的じゃないんですよね。add( A, B, Out )みたいな書き方よりも Out = add( A, B )のほうがわかりやすい気がします。

算術クラスの利用時には特にそう感じます。

命名の妥当性については考慮してませんでした。
例では単純化するために単に同じ名前にしました。
(でも人に使ってもらうときは命名大事ですね。)
最後に編集したユーザー ぼずお on 2013年11月01日(金) 19:41 [ 編集 1 回目 ]

ぼずお
記事: 1
登録日時: 12年前
住所: 中部地方

Re: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by ぼずお » 10年前

やっくんさん、初めまして。

>1つ目のコードの場合、最適化が無くともクラスにムーブコンストラクタを用意して
>受け取るというのもコピーせずに済むので良いかなと思ってます。

新しい言語の仕様ではそういったものがありましたね。
私ももしやと思って、簡単にコードを書いてみました。

CODE:

struct Matrix4x4
{
	Matrix4x4() {}

	//移動コンストラクタ
	Matrix4x4(Matrix4x4&& rv) {
		//結局中身のコピーが必要になってしまう。
		for (int y = 0; y < 4; y++)
		for (int x = 0; x < 4; x++)
			_m[y][x] = rv._m[y][x];
	}

	double _m[4][4];
};
よく考えてみると、
今回のようにプレーンな構造体の場合、結局のところ配列のコピーが必要になってしまうので、ムーブコンストラクタの意義はなさそうです。

ムーブコンストラクタが意義を持つのは以下のような実装です。
(このような実装が妥当かどうかはともかくとして)

CODE:

struct MatrixB
{
	MatrixB() {
		//ヒープから確保
		_m = new double[4*4];
	}
	~MatrixB() {
		//解放
		if (_m != nullptr)
			delete[] _m;
	}

	//移動コンストラクタ
	MatrixB(MatrixB&& rv) {
		//移動する
		_m = rv._m;	//ポインタの移動だけでok
		rv._m = nullptr;	//nullクリア
	}

	double*	_m;
};
内部をヒープでもっているような場合は、右辺値からポインタを移動するだけでよいので、無駄がありません。
最後に編集したユーザー ぼずお on 2013年11月01日(金) 21:05 [ 編集 2 回目 ]

beatle
記事: 1281
登録日時: 12年前

RE: 構造体を戻り値として丸ごと返してもいいものか

投稿記事 by beatle » 10年前

スマートポインタを生成するときはstd::make_sharedを使うと楽です。
戻り値でクラスを返したいなら、動的確保か値のコピーは発生しますから、動的確保のコストとクラス全体のコピーコストを比べて有利な方を取ればいいでしょうね。
RVOを期待して値返しするのも悪く無いかもしれません。

スマートポインタでのやり取りを半ば強制してしまうのは、それを意図している面もあるんです。
簡単に生ポインタに変換されてしまってはスマートポインタの利点がありませんので。
ぼずお さんが書きました: >beatleさん
おっとその発想がありましたか。
スマートポインタはあんまり使ったことないので以下間違っていたら済みません。

関数の中身はこのような形になるでしょうか。

CODE:

std::shared_ptr MakeSomeMatrix()
{
	std::shared_ptr mat(new Matrix44);
	//mat-> に対する値の設定
	return mat;
}

CODE:

//生成と計算(各関数は定義済みとして)
std::shared_ptr matA = MakeHogeMatrix();
std::shared_ptr matB = MakePiyoMatrix();
std::shared_ptr result = Multiply(matA, matB); 

//ローカル変数で受け取るとき(関数内で確保して、返すと即時破棄?)
Matrix4x4 localVal = *MakeSomeMatrix();
生成するたび計算するたびに動的確保が発生するのが気になりました。
パフォーマンス気にしないといった手前、動的確保にどうこう言うのは変ですかね・・
他の言語では普通なことっぽい?

スマートポインタでのやり取りを半ば強制してしまうのが少々難ありに感じました。

ただ、動的確保を前提とするような複雑なクラスの運用としては適しているかと思います。