6

C のレガシー コードで使用されている 2 つの大きな C 構造体があり、一方から他方へ、またはその逆に変換する必要があります。このようなもの :

#include <iostream>

struct A {
    int a;
    float b;
};
struct B {
    char a;
    int b;
};

struct C {
    A a;
    B b;
};

struct D {
    int a;
    char b;
    float c;
};

void CtoD( const C& c, D &d ) {
    d.a = c.a.a;
    d.b = c.b.a;
    d.c = c.a.b;
}
void DtoC( const D &d, C& c ) {
    c.a.a = d.a;
    c.b.a = d.b;
    c.a.b = d.c;
}

int main()
{
    C c = { { 1, 3.3f }, { 'a', 4 } };
    D d = { 1, 'b', 5.5f };

#if 0
    CtoD( c, d );
#else
    DtoC( d, c );
#endif

    std::cout<<"C="<<c.a.a<<" "<<c.a.b<<" "<<c.b.a<<" "<<c.b.b<<std::endl;
    std::cout<<"D="<<d.a<<" "<<d.b<<" "<<d.c<<std::endl;
}

関数CtoDDtoCは同じことをしていますが、反対方向です。1 つの構造を変更するには、両方を変更する必要があります。

エラーの可能性を最小限に抑え、繰り返しを避けるために、接続を一度だけ定義してから、ある値を別の値にコピーする、ある種のマッピングを実装したいと思います。このように、構造が変更された場合に必要な変更は 1 つだけです。

それで、問題は次のとおりです。それを行う方法は?おそらく使用できるデザインパターンはありますか?


私の実際の構造には何百ものフィールドがあります。上記は単純化した例です。

4

7 に答える 7

2

あなたの文字通りの例では、手間をかける価値はないと思います。変換がうまく機能することを確認するためのテストを作成するだけです。

実際のコードでは、構造体に「数百のフィールド」がある場合、構造体の設計が不適切である可能性があります。多分それらはより小さなオブジェクトで構成されるべきです。私はまったく同じ構造体オブジェクトに何百ものフィールドを必要とするものを設計したことはありません。代わりに、これらのフィールドはある種の分類を可能にして、より小さな束で処理できるようにしました。

あなたのコードはレガシーであり、それを書き直したくはないので、上記の例で述べたように、変換関数のテストを書くだけです。

十分にテストされたコードは、もはやレガシーコードではありません。レガシ コードとは、基本的に、自動化されたテストがないコードです。

書き換えが不可能な場合は、テストする必要があります。

「双方向」でテストするコストについては、以下の Idan Arye のコメントがすべてを語っています。

変換は対称的であるため、両方の方法でテストすることは、一方の方法でテストするよりもそれほど多くの作業ではありません。必要なのは、2 つの構造体を初期化し、それらを相互に変換されたバージョンに設定することだけですC cD d次に、それを確認するだけですCtoD(c)==dDtoC(d)==cまたは、たまたま定義されている場合は比較関数を使用します)。ここでの大きな作業は初期化cd- しかし、一方向の変換をテストしたい場合はとにかくそれを行う必要があるため、他の方法のテストを追加することは非常に安価です。

于 2013-10-15T09:29:30.160 に答える
1

いたずらしましょう...

struct rightwards_t {} rightwards;
struct leftwards_t {} leftwards;

template<typename Left, typename Right>
inline void map_field(Left& left, const Right& right, leftwards_t) {
    left = right;
}

template<typename Left, typename Right>
inline void map_field(const Left& left, Right& right, rightwards_t) {
    right = left;
}

template<typename Direction>
void convert(C& c, D& d, Direction direction) {
    map_field(c.a.a, d.a, direction);
    map_field(c.b.a, d.b, direction);
    map_field(c.a.b, d.c, direction);
}

// Usage
C c;
D d;
convert(c, d, leftwards); // Converts d into c
convert(c, d, rightwards); // Converts c into d

それが機能するかどうかは本当にわかりません (手元にコンパイラーがありません) が、私はそれを書きたかったのです。誰かが私がそれを正しくするのを手伝ってくれるなら、してください。

于 2013-10-15T12:16:46.563 に答える
0

std::pair関連するサブオブジェクトへの何百もの参照のコンテナでそれを行うことができます。参照を使用すると、読み取りと書き込みの両方ができるため、左のオブジェクトからの読み取りと右のオブジェクトへの書き込みは一方向に変換されます。反対は逆に変換します。

于 2013-10-15T09:31:48.877 に答える
0

これを行う方法を理解するのにしばらく時間がかかりました。そして、私は次の解決策を思いつきました:

#include <iostream>
#include <algorithm>
#include <cstring>

struct A {
    int a;
    float b;
};
struct B {
    char a;
    int b;
};

struct C {
    A a;
    B b;
};

struct D {
    int a;
    char b;
    float c;
};

template< typename T1, typename T2 >
struct DataField
{
    static inline void Update( const T1 & src, T2 & dst ) { dst = src; }
    static inline void Update( T1 & dst, const T2 & src ) { dst = src; }
};
template<>
struct DataField< const char*, char* >
{
    static inline void Update( const char* src, char* dst ) { strcpy( dst, src ); }
};
template<>
struct DataField< char*, const char* >
{
    static inline void Update( char* dst, const char* src ) { strcpy( dst, src ); }
};
template< typename T1, typename T2, int N  >
struct DataField< T1[N], T2[N] >
{
    static inline void Update( const T1 (&src)[N], T2 (&dst)[N] ) { std::copy_n( src, N, dst ); }
    static inline void Update( T1 (&dst)[N], const T1 (&src)[N] ) { std::copy_n( src, N, dst ); }
};

template< typename T1, typename T2 >
void UpdateDataField( T1 & src, T2 & dst )
{
    DataField< T1, T2 >::Update( src, dst );
}


template< typename T1, typename T2 >
void UpdateMappedDataFields( T1 & src, T2 & dst )
{
    UpdateDataField( src.a.a,  dst.a );
    UpdateDataField( src.a.b,  dst.c );

    UpdateDataField( src.b.a,  dst.b );
}

void CtoD( const C& c, D &d ) {
    UpdateMappedDataFields( c, d );
}
void DtoC( const D &d, C& c ) {
    UpdateMappedDataFields( c, d );
}

int main()
{
    C c = { { 1, 3.3f }, { 'a', 4 } };
    D d = { 1, 'b', 5.5f };

#if 0
    CtoD( c, d );
#else
    DtoC( d, c );
#endif

    std::cout<<"C="<<c.a.a<<" "<<c.a.b<<" "<<c.b.a<<" "<<c.b.b<<std::endl;
    std::cout<<"D="<<d.a<<" "<<d.b<<" "<<d.c<<std::endl;
}

すべてのデータ フィールドのマッピングは、UpdateMappedDataFields関数内でのみ行われます。

私が気に入らないのは、関数UpdateMappedDataFieldsがテンプレートであり、その実装方法によって、型が不明であるため、IDE を使用するときにオートコンプリートが妨げられることです。

しかし、もっと良い方法があれば聞きたいです。

于 2013-10-15T12:17:04.933 に答える
0

Idan と Dialecticus が提案したものと同様に、エディターの検索と置換機能を使用することもできます。たとえば、CtoD手動で書き込み、本文をコピーしてDtoC、Eclipse で使用します。

 Find:    ^(.*)=(.*);  
 Replace: $2=$1; 

の本体の各割り当ての左側と右側を自動的に交換するためDtoC

これが多かれ少なかれ複雑な C++ コンストラクトの使用よりも好ましいかどうかは、特定のコードと要件によって異なります。CtoD私の意見では、この方法の方がコードは読みやすく維持しやすいですが、もちろん、将来の変更と変更後の一貫性を強制するものは何もありませんDtoC(コードのコメントで手順について言及します)。

于 2013-10-15T15:25:20.540 に答える