如何在没有代码重复的情况下统一实现双向转换?

时间:2013-10-15 09:25:29

标签: c++ design-patterns data-structures

我在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正在做同样的事情,但方向相反。改变一个结构需要改变它们。

为了最大限度地减少错误的可能性,并避免重复,我想实现某种映射,其中我只定义连接一次,然后我将一个值复制到另一个。这样,如果结构发生变化,则只需要进行一次更改。

所以,问题是:怎么做?是否有可以使用的设计模式?


我的真实结构有数百个字段。以上只是简化示例。

7 个答案:

答案 0 :(得分:2)

在你的文字例子中,我认为这不值得麻烦。只需编写测试,以确保您的转换效果良好。

在您的真实代码中,如果您的结构具有“数百个字段”,则您的结构可能设计得很糟糕。也许它们应该由较小的物体组成。我从来没有在完全相同的结构对象中设计任何需要字段的东西 - 相反,这些字段允许某种分类,以便它们可以用更小的束处理。

由于您的代码是 legacy ,并且您不想重写它,只需为您的转换函数编写测试,正如我在上面为示例所述。

经过良好测试的代码不再是传统代码。遗留代码基本上是您没有自动化测试的代码。

如果重写它不是一个选项,那么必须对其进行测试。

关于“双向”测试成本,Idan Arye的comment below说了一切:

  

由于转换是对称的,因此两种方式进行测试并非如此   比单向测试更多的工作。你需要做的就是初始化两个   结构 - C cD d - 并将它们设置为转换后的版本   彼此。然后你只需检查CtoD(c)==d和   DtoC(d)==c(如果您碰巧拥有它们,则使用比较功能   定义)。这里的重要工作是初始化cd - 但你会这样做   如果你想测试单向转换,那么必须这样做   为其他方式添加测试非常便宜。

答案 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 ++结构取决于您的特定代码和要求。在我看来,代码更容易阅读和维护,但当然在未来的更改后,CtoDDtoC之间没有强制执行一致性(我在代码注释中提到了这个过程)。