2

我正在为一家用 C 编写其他所有东西的公司用 C++ 做一些工作(使用 C 对我来说不是一个选择:()。它们有许多非常相似的数据结构(即,它们都有字段例如“姓名”、“地址”等。但是,无论出于何种原因,都没有一个通用的结构可以用来作为其他所有内容的基础(这使得做任何事情都很糟糕)。任何人,我都需要做一个系统范围的分析内存中的这些结构,并将其全部放入一个表中。还不错,但表必须包含所有变量的所有字段的条目,即使它们没有字段(结构 b 可能有字段“延迟”,但结构 a 没有 - 在表中,每个实例的条目 a 必须有一个“延迟”的空条目。

所以,我的问题是,有没有办法在运行时确定已传递给模板函数的结构是否具有特定字段?还是我必须编写一些为我做的黑魔法宏?(问题基本上是我不能使用模板专业化)

谢谢!如果您有任何问题,请随时提问!

这是我在想什么的片段......

struct A
{
  char name[256];
  int index;
  float percision;
};

struct B
{
  int index;
  char name[256];
  int latency;
};

/* More annoying similar structs... note that all of the above are defined in files that were compiled as C - not C++ */

struct Entry
{
  char name[256];
  int index;
  float percision;
  int latency;
  /* more fields that are specific to only 1 or more structure */
};

template<typename T> struct Entry gatherFrom( T *ptr )
{
  Entry entry;

  strcpy( entry.name, ptr->name, strlen( ptr->name ) );
  entry.index = ptr->index;
  /* Something like this perhaps? */
  entry.percision = type_contains_field( "percision" ) ? ptr->percision : -1;
}

int main()
{
  struct A a;
  struct B b;

  /* initialization.. */

  Entry e  = gatherFrom( a );
  Entry e2 = gatherFrom ( b );

  return 0;
}
4

3 回答 3

3

您可以在编译时执行此操作,而无需触及原始结构的源代码:

#include <iostream>
#include <limits>
#include <memory.h>

struct A
{
    char name[256];
    int index;
    float percision;
};

struct B
{
    int index;
    char name[256];
    int latency;
};

struct Entry
{
    char name[256];
    int index;
    float percision;
    int latency;
    /* more fields that are specific to only 1 or more structure */
};

inline
std::ostream & operator<<(std::ostream & os, Entry const & e) {
    return os << e.name << "{" << e.index << ", " << e.percision << ", " << e.latency << "}";
}

template <typename T>
inline
void assign(T & dst, T const & src) {
    dst = src;
}

template <size_t N>
inline
void assign(char (&dst)[N], char const (&src)[N]) {
    memcpy(dst, src, N);
}

#define DEFINE_ENTRY_FIELD_COPIER(field)                            \
    template <typename T>                                           \
    inline                                                          \
    decltype(T::field, true) copy_##field(T const * t, Entry & e) { \
        assign(e.field, t->field);                                  \
        return true;                                                \
    }                                                               \
                                                                    \
    inline                                                          \
    bool copy_##field(void const *, Entry &) {                      \
            return false;                                           \
    }

DEFINE_ENTRY_FIELD_COPIER(name)
DEFINE_ENTRY_FIELD_COPIER(index)
DEFINE_ENTRY_FIELD_COPIER(percision)
DEFINE_ENTRY_FIELD_COPIER(latency)

template <typename T>
Entry gatherFrom(T const & t) {
    Entry e = {"", -1, std::numeric_limits<float>::quiet_NaN(), -1};
    copy_name(&t, e);
    copy_index(&t, e);
    copy_percision(&t, e);
    copy_latency(&t, e);
    return e;
}

int main() {
    A a = {"Foo", 12, 1.2};
    B b = {23, "Bar", 34};

    std::cout << "a = " << gatherFrom(a) << "\n";
    std::cout << "b = " << gatherFrom(b) << "\n";
}

DEFINE_ENTRY_FIELD_COPIER()宏为您要提取的每个字段定义了一对重载函数。一个重载 ( copy_##field(T const * t, …), 变成copy_name(T const * t, …),copy_index(T const * t, …)等) 将其返回类型定义为,如果有一个名为,的数据成员decltype(T::field, true), 则解析为 type 。如果没有这样的字段, 替换失败, 但不会导致编译-time 错误,第一个重载被简单地视为它不存在(这称为SFINAE),因此调用解析为第二个重载 ,它的第一个参数完全接受任何类型并且什么都不做。boolTnameindexTcopy_##field(void const * t, …)

笔记:

  1. 因为此代码在编译时解决了重载,gatherFrom()所以是最佳的,gatherFrom<A>()例如,生成的二进制代码看起来就像您手动调整它一样A

    Entry handCraftedGatherFromA(A const & a) {
        Entry e;
        e.latency = -1;
        memcpy(_result.name, a.name, sizeof(a.name));
        e.index = a.index;
        e.percision = a.percision;
        return e;
    }
    

    在 g++ 4.8 下,使用-O3,gatherFrom<A>()handCraftedGatherFromA()生成相同的代码:

    pushq   %rbx
    movl    $256, %edx
    movq    %rsi, %rbx
    movl    $-1, 264(%rdi)
    call    _memcpy
    movss   260(%rbx), %xmm0
    movq    %rax, %rcx
    movl    256(%rbx), %eax
    movss   %xmm0, 260(%rcx)
    movl    %eax, 256(%rcx)
    movq    %rcx, %rax
    popq    %rbx
    ret
    

    不幸的是, Clang 4.2 的gatherFrom<A>()表现不佳;它冗余地对整个条目进行零初始化。所以我猜这不全是玫瑰。

    通过使用NRVOe ,两个版本在返回时都避免了复制。movq %rcx, %rax但是,我应该注意,两个版本都将通过使用输出参数而不是返回值来保存一个操作码 ( )。

  2. 这些copy_…()函数返回一个bool结果,指示复制是否发生。这目前没有使用,但它可以用于,例如,定义int Entry::validFields为指示填充了哪些字段的位掩码。

  3. 不需要宏;它只是为了DRY。基本成分是使用 SFINAE。

  4. assign()也不需要重载。他们只是避免使用不同的几乎相同的宏来处理 char 数组。

  5. 上面的代码依赖于 C++11 的 decltype 关键字。如果您使用的是较旧的编译器,它会更混乱,但仍然可能。我设法想出的最干净的解决方案如下。它符合 C++98 标准并且仍然基于 SFINAE 原则:

    template <typename C, typename F, F (C::*), typename T>
    struct EnableCopy {
        typedef T type;
    };
    
    #define DEFINE_ENTRY_FIELD_COPIER(field, ftype)             \
        template <typename T>                                   \
        inline                                                  \
        typename EnableCopy<T, ftype, &T::field, bool>::type    \
        copy_##field(T const * t, Entry & e) {                  \
            copy_value(e.field, t->field);                      \
            return true;                                        \
        }                                                       \
                                                                \
        inline                                                  \
        bool copy_##field(void const *, Entry &) {              \
            return false;                                       \
        }
    
    DEFINE_ENTRY_FIELD_COPIER(name     , char[256]);
    DEFINE_ENTRY_FIELD_COPIER(index    , int);
    DEFINE_ENTRY_FIELD_COPIER(percision, float);
    DEFINE_ENTRY_FIELD_COPIER(latency  , int);
    

    您还必须放弃 C++11 的可移植性std::numeric_limits<float>::quiet_NaN()并使用一些技巧(0.0f/0.0f似乎有效)或选择另一个神奇的值。

于 2013-07-17T02:00:35.843 回答
2

用 C 编写的所有其他内容(使用 C 对我来说不是一个选项:()。

首先,我想引用 Linus Torvalds 对这个问题的看法:


From: Linus Torvalds <torvalds <at> linux-foundation.org>
Subject: Re: [RFC] Convert builin-mailinfo.c to use The Better String Library.
Newsgroups: gmane.comp.version-control.git
Date: 2007-09-06 17:50:28 GMT (2 years, 14 weeks, 16 hours and 36 minutes ago)

C++ is a horrible language. It's made more horrible by the fact that a lot 
of substandard programmers use it, to the point where it's much much 
easier to generate total and utter crap with it. Quite frankly, even if 
the choice of C were to do *nothing* but keep the C++ programmers out, 
that in itself would be a huge reason to use C.

http://harmful.cat-v.org/software/c++/linus


它们有许多非常相似的数据结构(即,它们都具有诸如“名称”、“地址”等字段。但是,无论出于何种原因,它们都没有一个通用的结构来作为其他一切的基础的(使做任何事情都变得地狱)。

他们可能有非常充分的理由。将公共字段放入单个基础结构(类)中听起来是个好主意。但是,如果您想对其中一个结构进行重大更改(替换某些字段、更改类型等),同时保持其余部分不变,这会使事情变得非常困难。OOP 肯定不是一种真正的做事方式。

所以,我的问题是,有没有办法在运行时确定已传递给模板函数的结构是否具有特定字段?

不,这是不可能的。在 C 和 C++ 中都没有,因为在创建二进制文件时,所有关于类型的信息都会被丢弃。C 或 C++ 中既没有反射也没有内省。好吧,从技术上讲,编译器发出的调试信息确实提供了此信息,但没有语言内置功能可以访问此信息。此外,这种调试信息依赖于在编译时执行的分析,而不是在运行时。C++ 有 RTTI,但这只是一个非常粗略的系统,用于识别实例关闭的类。它对类或结构成员没有帮助。

但是你为什么要在运行时这样做呢?

Anywho,我需要对内存中的这些结构进行系统范围的分析,并将其全部放入一个表中。

你应该很高兴你必须分析 C 而不是 C++。因为 C 非常非常容易解析(不像 C++,它非常难以解析,主要是因为那些该死的模板)。尤其是结构。我只需编写一个小而简单的脚本,从 C 源代码中提取所有结构定义。然而,由于结构的大小是恒定的,它们通常包含指向动态分配数据的指针。除非您想修补分配器,否则我认为最简单的分析方法是连接到调试器并记录指针分配给结构成员的每个唯一对象的内存使用情况。

于 2012-07-05T22:23:18.160 回答
1

是的,这一点都不难。只需将 anA和 anEntry放在一个对象中,然后将其Entry设为二等公民:

void setDefaultValues(Entry*); // You should be able to provide these.
struct Entry {
  int x;
  int y;
};
struct Indirect : public Entry { };
template<typename T> struct EntryOr : public T, Indirect
{
  setDefaultValues(this);
};

// From C code
struct A {
  int x;
}

int main()
{
  EntryOr<A> foo;
  foo.x = 5; // A::x
  std::cout << foo.x << foo.y; // Prints A::x and Entry::y
}

(关联)

于 2012-07-06T14:13:36.620 回答