6

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

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

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


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

4

7 回答 7

2

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

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

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

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

如果重写它不是一种选择,那么测试它是必须的。

关于“双向”测试的成本,Idan Arye 在下面的评论说明了一切:

由于转换是对称的,因此以两种方式测试它并不比以一种方式测试它多得多。您需要做的就是初始化两个结构 -C cD d- 并将它们设置为彼此的转换版本。然后你只需要检查那个CtoD(c)==dand DtoC(d)==c(或者如果你碰巧定义了它们,就使用比较函数)。这里的主要工作是初始化c-d但是如果你想测试一种方式的转换,无论如何你都必须这样做,所以为另一种方式添加测试非常便宜。

于 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

选择你最喜欢的脚本语言(如果你还没有,我推荐 Ruby)并编写一个小脚本来为你生成转换函数(源文件和头文件)。

除非您选择蹩脚的脚本语言,否则在调用生成转换器的函数时,您甚至可以直接用该语言表示连接。例如,在 Ruby 中定义之后,generate_converters您可以编写:

generate_converters :C,:D do
    convert 'a.a','a'
    convert 'b.a','b'
    convert 'a.b','c'
end
于 2013-10-15T09:40:41.510 回答
0

我同意 Daniel 的观点,不值得麻烦,但您可以编写一个小应用程序来为您生成代码。您向应用程序提供两个结构的描述以及结构成员之间的绑定,然后应用程序生成 C 代码,然后照常编译。

另一种选择是摆弄指向 members的指针,但这可能会消耗更多开发人员的时间,因此比第一个选项更不值得麻烦。

于 2013-10-15T09:40:45.027 回答
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; 

为了自动交换 .body 中每个赋值的左侧和右侧DtoC

这是否比使用或多或少复杂的 c++ 构造更可取取决于您的特定代码和要求。在我看来,以这种方式代码更易于阅读和维护,但当然没有任何东西可以在未来更改之间CtoDDtoC之后强制执行一致性(我会在代码注释中提到该过程)。

于 2013-10-15T15:25:20.540 回答