[英]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;
}
功能CtoD
和DtoC
正在做同樣的事情,但方向相反。 改變一個結構需要改變它們。
為了最大限度地減少錯誤的可能性,並避免重復,我想實現某種映射,其中我只定義連接一次,然后我將一個值復制到另一個。 這樣,如果結構發生變化,則只需要進行一次更改。
所以,問題是:怎么做? 可能有我可以使用的設計模式嗎?
我的真實結構有數百個領域。 以上只是簡化的例子。
在你的文字例子中,我認為這不值得麻煩。 只需編寫測試,以確保您的轉化效果良好。
在您的真實代碼中,如果您的結構具有“數百個字段”,則您的結構可能設計得很糟糕。 也許它們應該由較小的物體組成。 我從來沒有在完全相同的結構對象中設計任何需要字段的東西 - 相反,這些字段允許某種分類,以便它們可以在較小的束中處理。
由於您的代碼是遺留的,並且您不想重寫它,因此只需為您的轉換函數編寫測試,正如我在上面為示例所述。
經過良好測試的代碼不再是遺留代碼。 遺留代碼基本上是您沒有自動化測試的代碼。
如果重寫它不是一個選項,測試它是必須的。
關於“兩種方式”測試它的成本,Idan Arye在下面的評論說明了一切:
由於轉換是對稱的,因此對它進行雙向測試並不比單向測試更多。 您需要做的就是初始化兩個結構--C
C c
和D d
- 並將它們設置為彼此的轉換版本。 然后你只需要檢查CtoD(c)==d
和DtoC(d)==c
(或者如果你碰巧定義它們就使用比較函數)。 這里的重要工作是初始化c
和d
- 但如果你想測試單向轉換,你必須這樣做,所以為另一種方式添加測試非常便宜。
讓我們變得頑皮......
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 ++結構更可取決於您的特定代碼和要求。 在我看來,代碼更容易閱讀和維護這種方式,但當然在未來的更改之后沒有任何東西強制CtoD
和DtoC
之間的一致性(我在代碼注釋中提到過程)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.