我在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
正在做同样的事情,但方向相反。改变一个结构需要改变它们。
为了最大限度地减少错误的可能性,并避免重复,我想实现某种映射,其中我只定义连接一次,然后我将一个值复制到另一个。这样,如果结构发生变化,则只需要进行一次更改。
所以,问题是:怎么做?是否有可以使用的设计模式?
我的真实结构有数百个字段。以上只是简化示例。
答案 0 :(得分:2)
在你的文字例子中,我认为这不值得麻烦。只需编写测试,以确保您的转换效果良好。
在您的真实代码中,如果您的结构具有“数百个字段”,则您的结构可能设计得很糟糕。也许它们应该由较小的物体组成。我从来没有在完全相同的结构对象中设计任何需要字段的东西 - 相反,这些字段允许某种分类,以便它们可以用更小的束处理。
由于您的代码是 legacy ,并且您不想重写它,只需为您的转换函数编写测试,正如我在上面为示例所述。
经过良好测试的代码不再是传统代码。遗留代码基本上是您没有自动化测试的代码。
如果重写它不是一个选项,那么必须对其进行测试。
关于“双向”测试成本,Idan Arye的comment below说了一切:
由于转换是对称的,因此两种方式进行测试并非如此 比单向测试更多的工作。你需要做的就是初始化两个 结构 -
C c
和D d
- 并将它们设置为转换后的版本 彼此。然后你只需检查CtoD(c)==d
和DtoC(d)==c
(如果您碰巧拥有它们,则使用比较功能 定义)。这里的重要工作是初始化c
和d
- 但你会这样做 如果你想测试单向转换,那么必须这样做 为其他方式添加测试非常便宜。
答案 1 :(得分: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
真的不知道它是否有效(手头没有编译器),但我想写它。如果有人能帮我把它弄好,请做。
答案 2 :(得分:0)
你可以使用一个容器,对所涉及的子对象进行数百次std::pair
个引用。使用引用,您既可以读取也可以写入,因此从左对象读取和写入右对象可以单向转换。相反的另一种方式转换。
答案 3 :(得分:0)
选择您最喜欢的脚本语言(如果您还没有我推荐的Ruby),并编写一个小脚本,为您生成转换函数(包括源文件和头文件)。
除非您选择一种蹩脚的脚本语言,否则在调用生成转换器的函数时,您甚至可以直接用该语言表示连接。例如,在定义generate_converters
之后的Ruby中,你可以写:
generate_converters :C,:D do
convert 'a.a','a'
convert 'b.a','b'
convert 'a.b','c'
end
答案 4 :(得分:0)
我同意丹尼尔,不值得麻烦,但你可以写一个小应用程序为你生成代码。您为应用程序提供了两个结构的描述,以及结构成员之间的绑定,然后应用程序生成C代码,然后像往常一样进行编译。
另一种选择是摆弄pointers to members,但这可能会消耗更多开发人员的时间,所以比第一种选择更不值得麻烦。
答案 5 :(得分: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时会阻止自动完成,因为这些类型是未知的。
但是,我仍然想听听是否有更好的方法。
答案 6 :(得分:0)
与Idan和Dialecticus提出的相似,您也可以使用编辑器的搜索和替换功能:
例如。手动编写CtoD
,将正文复制到DtoC
,然后 - 在eclipse中使用
Find: ^(.*)=(.*);
Replace: $2=$1;
以自动交换DtoC
正文中每个作业的左侧和右侧。
这是否优于使用或多或少复杂的c ++结构取决于您的特定代码和要求。在我看来,代码更容易阅读和维护,但当然在未来的更改后,CtoD
和DtoC
之间没有强制执行一致性(我在代码注释中提到了这个过程)。