C++ で (スタック上で宣言された) 配列を処理するにはどうすればよいですか?
質問
結果を配列メンバーに保持する行列を解析するクラスがあります。
class Parser
{
...
double matrix_[4][4];
};
このクラスのユーザーは、次のような API 関数を呼び出す必要があります (関数は制御できないため、インターフェースを変更して動作を容易にすることはできません)。
void api_func(const double matrix[4][4]);
呼び出し元が配列の結果を関数に渡すために私が思いついた唯一の方法は、メンバーをパブリックにすることです。
void myfunc()
{
Parser parser;
...
api_func(parser.matrix_);
}
これが唯一の方法でしょうか?このように宣言された多次元配列がいかに柔軟性に欠けているかに驚いています。私は思った matrix_
本質的には double**
そして私は 2 つの間に (安全に) キャストすることができました。結局のところ、見つけることさえできません 危険な 物の間にキャストする方法。アクセサを追加するとします。 Parser
クラス:
void* Parser::getMatrix()
{
return (void*)matrix_;
}
これはコンパイルされますが、奇妙な配列型にキャストバックする方法がないようなので、使用できません。
// A smorgasbord of syntax errors...
api_func((double[][])parser.getMatrix());
api_func((double[4][4])parser.getMatrix());
api_func((double**)parser.getMatrix()); // cast works but it's to the wrong type
エラーは次のとおりです。
エラー C2440:'タイプキャスト' :「void *」から「const double [4][4]」に変換できません
...興味深い補足が付いています:
配列への参照またはポインターへの変換はありますが、配列型への変換はありません。
おそらくここでは役に立たないかもしれませんが、参照または配列へのポインタにキャストする方法も判断できません。
確かに、この時点ではこの問題は純粋に学術的なものです。 void*
キャストは、公開された単一のクラス メンバーよりもクリーンとは言えません。
解決
ここに素敵でクリーンな方法があります:
class Parser
{
public:
typedef double matrix[4][4];
// ...
const matrix& getMatrix() const
{
return matrix_;
}
// ...
private:
matrix matrix_;
};
ここでは、配列ではなく説明的な型名を使用していますが、これは typedef
コンパイラは、基本型を受け取る変更不可能な API 関数にそれを渡すことを引き続き許可します。
他のヒント
これを試して。gcc 4.1.3 では正常にコンパイルされます。
typedef double FourSquare[4][4];
class Parser
{
private:
double matrix_[4][4];
public:
Parser()
{
for(int i=0; i<4; i++)
for(int j=0; j<4; j++)
matrix_[i][j] = i*j;
}
public:
const FourSquare& GetMatrix()
{
return matrix_;
}
};
void api_func( const double matrix[4][4] )
{
}
int main( int argc, char** argv )
{
Parser parser;
api_func( parser.GetMatrix() );
return 0;
}
私は過去に行列を渡すために次のような共用体を使用しました。
union matrix {
double dflat[16];
double dmatr[4][4];
};
次に、ポインターをセッターに渡し、データをクラス内の行列にコピーします。
これを別の方法で処理する (より一般的な) 方法もありますが、私の経験では、この解決策が最終的に最もクリーンになる傾向があります。
私は、matrix_ は基本的に double** と同じだと思っていました。
C には、配列へのポインタの配列ではなく、真の多次元配列があるため、double[4][4] は 4 つの double[4] 配列の連続した配列であり、(double ではなく double[16] と同等です) *)[4]。
配列タイプへの変換はありませんが、2倍の[4] [4]に値をキャストする配列への参照またはポインターへの変換は、STD :: String(parser.getMatrix( ) - アレイが適切なコンストラクターを供給しないことを除いて。おそらく、たとえできたとしても、そんなことはしたくなかったでしょう。
型はストライドをエンコードするため、完全な型が必要です (double[][] は使用できません)。void* を ((double[4][4])*) にキャストして再解釈し、参照を取得できます。ただし、最初に行列を typedef して正しい型の参照を返すのが最も簡単です。
typedef double matrix_t[4][4];
class Parser
{
double matrix_[4][4];
public:
void* get_matrix () { return static_cast<void*>(matrix_); }
const matrix_t& get_matrix_ref () const { return matrix_; }
};
int main ()
{
Parser p;
matrix_t& data1 = *reinterpret_cast<matrix_t*>(p.get_matrix());
const matrix_t& data2 = p.get_matrix_ref();
}
選択した回答について詳しく説明するには、次の行を参照してください
const matrix& getMatrix() const
これは素晴らしいことです。ポインターやキャストについて心配する必要はありません。あなたは 参照 基になる行列オブジェクトに。私の意見では、参照は C++ の最も優れた機能の 1 つですが、ストレート C でコーディングする場合にはこれを見逃してしまいます。
C++ における参照とポインターの違いに慣れていない場合は、次のようにしてください。 これを読む
いずれにしても、次のような場合には注意が必要です。 Parser
基になる行列オブジェクトを実際に所有するオブジェクトがスコープ外になると、その参照を介して行列にアクセスしようとするコードはスコープ外のオブジェクトを参照することになり、クラッシュします。