簡體   English   中英

如何在沒有代碼重復的情況下統一實現雙向轉換?

[英]How to uniformly implement two-way conversion without code repetition?

我在C遺留代碼中使用了兩個大的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正在做同樣的事情,但方向相反。 改變一個結構需要改變它們。

為了最大限度地減少錯誤的可能性,並避免重復,我想實現某種映射,其中我只定義連接一次,然后我將一個值復制到另一個。 這樣,如果結構發生變化,則只需要進行一次更改。

所以,問題是:怎么做? 可能有我可以使用的設計模式嗎?


我的真實結構有數百個領域。 以上只是簡化的例子。

在你的文字例子中,我認為這不值得麻煩。 只需編寫測試,以確保您的轉化效果良好。

在您的真實代碼中,如果您的結構具有“數百個字段”,則您的結構可能設計得很糟糕。 也許它們應該由較小的物體組成。 我從來沒有在完全相同的結構對象中設計任何需要字段的東西 - 相反,這些字段允許某種分類,以便它們可以在較小的束中處理。

由於您的代碼是遺留的,並且您不想重寫它,因此只需為您的轉換函數編寫測試,正如我在上面為示例所述。

經過良好測試的代碼不再是遺留代碼。 遺留代碼基本上是您沒有自動化測試的代碼。

如果重寫它不是一個選項,測試它是必須的。

關於“兩種方式”測試它的成本,Idan Arye在下面評論說明了一切:

由於轉換是對稱的,因此對它進行雙向測試並不比單向測試更多。 您需要做的就是初始化兩個結構--C C cD d - 並將它們設置為彼此的轉換版本。 然后你只需要檢查CtoD(c)==dDtoC(d)==c (或者如果你碰巧定義它們就使用比較函數)。 這里的重要工作是初始化cd - 但如果你想測試單向轉換,你必須這樣做,所以為另一種方式添加測試非常便宜。

讓我們變得頑皮......

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

真的不知道它是否有效(手頭沒有編譯器),但我想寫它。 如果有人能幫助我糾正,請做。

你可以使用一個包含數百個std::pair所涉及的子對象的引用的容器來完成它。 使用引用,您既可以讀取也可以寫入,因此從左對象讀取和寫入右對象可以單向轉換。 相反的另一種方式轉換。

選擇您喜歡的腳本語言(如果您還沒有我推薦的Ruby)並編寫一個小腳本,為您生成轉換函數(源文件和頭文件)。

除非您選擇一種蹩腳的腳本語言,否則在調用生成轉換器的函數時,您甚至可以直接用語言表示連接。 例如,在定義generate_converters之后的Ruby中,您可以編寫:

generate_converters :C,:D do
    convert 'a.a','a'
    convert 'b.a','b'
    convert 'a.b','c'
end

我同意丹尼爾,不值得麻煩,但你可以寫一個小應用程序為你生成代碼。 您為應用程序提供了兩個結構的描述,以及結構成員之間的綁定,然后應用程序生成C代碼,然后像往常一樣進行編譯。

另一個選擇是指向成員的指針 ,但這可能會消耗更多的開發人員的時間,所以更不值得麻煩比第一個選項。

我花了一段時間才弄清楚如何做到這一點。 我出來了下一個解決方案:

#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時會阻止自動完成,因為這些類型是未知的。

但是,我仍然想聽聽是否有更好的方法。

與Idan和Dialecticus提出的類似,您也可以使用編輯器的搜索和替換功能:例如,手動編寫CtoD ,將正文復制到DtoC以及 - 在eclipse中使用

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

為了自動交換DtoC正文中每個作業的左側和右側。

這是否比使用或多或少復雜的c ++結構更可取決於您的特定代碼和要求。 在我看來,代碼更容易閱讀和維護這種方式,但當然在未來的更改之后沒有任何東西強制CtoDDtoC之間的一致性(我在代碼注釋中提到過程)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM